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](doc/smartdns-banner.png) 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) ## 软件效果展示 ### 仪表盘 ![SmartDNS-WebUI](doc/smartdns-webui.png) ### 速度对比 **阿里 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。 ## 架构 ![Architecture](https://github.com/pymumu/test/releases/download/blob/architecture.png) 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 贝宝 [![Support via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://paypal.me/PengNick/) ### AliPay 支付宝 ![alipay](doc/alipay_donate.jpg) ### WeChat Pay 微信支付 ![wechat](doc/wechat_donate.jpg) ## 开源声明 SmartDNS 基于 GPL V3 协议开源。 ================================================ FILE: ReadMe_en.md ================================================ # SmartDNS ![SmartDNS](doc/smartdns-banner.png) 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 ![SmartDNS-WebUI](doc/smartdns-webui.png) ### 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 ![Architecture](doc/architecture.png) 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 [![Support via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://paypal.me/PengNick/) ### Alipay ![alipay](doc/alipay_donate.jpg) ### Wechat ![wechat](doc/wechat_donate.jpg) ## 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 ================================================

<%:Collecting data...%>

================================================ 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>>, is_init: Mutex, is_run: Mutex, plugin: Mutex>, } impl DataServerControl { pub fn new() -> Self { DataServerControl { data_server: Arc::new(DataServer::new()), server_thread: Mutex::new(None), is_init: Mutex::new(false), is_run: Mutex::new(false), plugin: Mutex::new(Weak::new()), } } pub fn get_data_server(&self) -> Arc { Arc::clone(&self.data_server) } pub fn set_plugin(&self, plugin: Arc) { *self.plugin.lock().unwrap() = Arc::downgrade(&plugin); } pub fn get_plugin(&self) -> Result, Box> { let plugin = match self.plugin.lock() { Ok(plugin) => plugin, Err(_) => return Err("Failed to lock plugin mutex".into()), }; if let Some(plugin) = plugin.upgrade() { return Ok(plugin); } Err("Plugin is not set".into()) } pub fn init_db(&self, conf: &DataServerConfig) -> Result<(), Box> { let inner_clone = Arc::clone(&self.data_server); let ret = inner_clone.init_server(conf); if let Err(e) = ret { return Err(e); } *self.is_init.lock().unwrap() = true; Ok(()) } pub fn start_data_server(&self) -> Result<(), Box> { let inner_clone = Arc::clone(&self.data_server); if *self.is_init.lock().unwrap() == false { return Err("data server not init".into()); } let plugin = self.get_plugin()?; self.data_server.set_plugin(plugin.clone()); let rt = plugin.get_runtime(); let server_thread = rt.spawn(async move { let ret = DataServer::data_server_loop(inner_clone).await; if let Err(e) = ret { dns_log!(LogLevel::ERROR, "data server error: {}", e); Plugin::smartdns_exit(1); } dns_log!(LogLevel::DEBUG, "data server exit."); }); *self.is_run.lock().unwrap() = true; *self.server_thread.lock().unwrap() = Some(server_thread); Ok(()) } pub fn stop_data_server(&self) { if *self.is_run.lock().unwrap() == false { return; } self.data_server.stop_data_server(); let _server_thread = self.server_thread.lock().unwrap().take(); if let Some(server_thread) = _server_thread { let plugin = self.get_plugin(); if plugin.is_err() { dns_log!( LogLevel::ERROR, "get plugin error: {}", plugin.err().unwrap() ); return; } let plugin = plugin.unwrap(); let rt = plugin.get_runtime(); tokio::task::block_in_place(|| { if let Err(e) = rt.block_on(server_thread) { dns_log!(LogLevel::ERROR, "http server stop error: {}", e); } }); } *self.is_run.lock().unwrap() = false; } pub fn send_request(&self, request: Box) -> Result<(), Box> { if request.is_prefetch_request() { return Ok(()); } self.data_server.get_stat().add_qps_count(1); if self.data_server.is_handle_request_disabled() { return Ok(()); } if let Some(tx) = self.data_server.data_tx.as_ref() { let ret = tx.try_send(request); if let Err(e) = ret { self.data_server.get_stat().add_request_drop(1); return Err(e.to_string().into()); } } Ok(()) } pub fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32) { self.data_server.server_log(level, msg, msg_len); } pub fn server_audit_log(&self, msg: &str, msg_len: i32) { self.data_server.server_audit_log(msg, msg_len); } } impl Drop for DataServerControl { fn drop(&mut self) { self.stop_data_server(); } } pub struct DataServer { conf: Arc>, notify_tx: Option>, notify_rx: Mutex>>, data_tx: Option>>, data_rx: Mutex>>>, db: Arc, disable_handle_request: AtomicBool, stat: Arc, server_log: ServerLog, server_audit_log: ServerAuditLog, plugin: Mutex>, whois: whois::WhoIs, startup_timestamp: u64, recv_in_batch: Mutex, mac_cache: Mutex>, client_pending_list: Mutex>, } impl DataServer { pub fn new() -> Self { let db = Arc::new(DB::new()); let conf = Arc::new(RwLock::new(DataServerConfig::new())); let mut plugin = DataServer { conf: conf.clone(), notify_tx: None, notify_rx: Mutex::new(None), data_tx: None, data_rx: Mutex::new(None), db: db.clone(), stat: DataStats::new(db, conf.clone()), server_log: ServerLog::new(), server_audit_log: ServerAuditLog::new(), plugin: Mutex::new(Weak::new()), whois: whois::WhoIs::new(), startup_timestamp: get_utc_time_ms(), disable_handle_request: AtomicBool::new(false), recv_in_batch: Mutex::new(true), mac_cache: Mutex::new(HashMap::new()), client_pending_list: Mutex::new(HashMap::new()), }; let (tx, rx) = mpsc::channel(100); plugin.notify_tx = Some(tx); plugin.notify_rx = Mutex::new(Some(rx)); let (tx, rx) = mpsc::channel(1024 * 256); plugin.data_tx = Some(tx); plugin.data_rx = Mutex::new(Some(rx)); plugin } pub fn get_recv_in_batch(&self) -> bool { *self.recv_in_batch.lock().unwrap() } pub fn set_recv_in_batch(&self, recv_in_batch: bool) { *self.recv_in_batch.lock().unwrap() = recv_in_batch; } fn init_server(&self, conf: &DataServerConfig) -> Result<(), Box> { let mut conf_clone = self.conf.write().unwrap(); *conf_clone = conf.clone(); smartdns::smartdns_enable_update_neighbour(true); if utils::is_dir_writable(&conf_clone.data_path) == false { return Err(format!( "data path '{}' is not exist or writable.", conf_clone.data_path ) .into()); } conf_clone.db_file = conf_clone.data_path.clone() + "/" + DB_FILE_NAME; dns_log!(LogLevel::INFO, "open db: {}", conf_clone.db_file); let ret = self.db.open(&conf_clone.db_file); if let Err(e) = ret { return Err(e); } let ret = self.stat.init(); if let Err(e) = ret { return Err(e); } Ok(()) } pub fn set_plugin(&self, plugin: Arc) { *self.plugin.lock().unwrap() = Arc::downgrade(&plugin); } pub fn get_plugin(&self) -> Result, Box> { let plugin = match self.plugin.lock() { Ok(plugin) => plugin, Err(_) => return Err("Failed to lock plugin mutex".into()), }; if let Some(plugin) = plugin.upgrade() { return Ok(plugin); } Err("Plugin is not set".into()) } pub fn get_data_server_config(&self) -> DataServerConfig { let conf = self.conf.read().unwrap(); conf.clone() } pub fn get_config(&self, key: &str) -> Option { let ret = self.db.get_config(key); if let Ok(value) = ret { return value; } None } pub fn get_server_config_from_file(&self, key: &str) -> Option { let ret = Plugin::dns_conf_plugin_config(key); if let Some(value) = ret { return Some(value); } None } pub fn get_server_config(&self, key: &str) -> Option { let ret = self.get_config(key); if let Some(value) = ret { return Some(value); } let ret = Plugin::dns_conf_plugin_config(key); if let Some(value) = ret { return Some(value); } None } pub async fn whois(&self, domain: &str) -> Result> { self.whois.query(domain).await } pub fn get_config_list(&self) -> Result, Box> { self.db.get_config_list() } pub fn set_config(&self, key: &str, value: &str) -> Result<(), Box> { self.db.set_config(key, value) } pub fn get_upstream_server_list(&self) -> Result, Box> { let servers = UpstreamServerInfo::get_all()?; Ok(servers) } pub fn get_domain_list( &self, param: &DomainListGetParam, ) -> Result> { self.db.get_domain_list(Some(param)) } pub fn get_domain_list_count(&self) -> u64 { self.db.get_domain_list_count(None) } pub fn delete_domain_by_id(&self, id: u64) -> Result> { self.db.delete_domain_by_id(id) } pub fn delete_domain_before_timestamp(&self, timestamp: u64) -> Result> { self.db.delete_domain_before_timestamp(timestamp) } pub fn delete_client_by_id(&self, id: u64) -> Result> { self.db.delete_client_by_id(id) } pub fn get_client_list( &self, param: &ClientListGetParam, ) -> Result> { self.db.get_client_list(Some(param)) } pub fn get_top_client_top_list( &self, count: Option, ) -> Result, Box> { self.db.get_client_top_list(count.unwrap_or(10)) } pub fn get_top_domain_top_list( &self, count: Option, ) -> Result, Box> { self.db.get_domain_top_list(count.unwrap_or(10)) } pub fn get_hourly_query_count( &self, past_hours: Option, ) -> Result> { self.db.get_hourly_query_count(past_hours.unwrap_or(24)) } pub fn get_daily_query_count( &self, past_days: Option, ) -> Result> { self.db.get_daily_query_count(past_days.unwrap_or(30)) } pub fn get_stat(&self) -> Arc { self.stat.clone() } pub fn get_metrics(&self) -> Result> { let metrics = MetricsData { total_query_count: self.stat.get_total_request(), block_query_count: self.stat.get_total_blocked_request(), request_drop_count: self.stat.get_request_drop(), fail_query_count: self.stat.get_total_failed_request(), avg_query_time: smartdns::Stats::get_avg_process_time(), cache_hit_rate: smartdns::Stats::get_cache_hit_rate(), cache_number: smartdns::Plugin::dns_cache_total_num() as u64, cache_memory_size: smartdns::Stats::get_cache_memory_size(), qps: self.stat.get_qps(), memory_usage: self.stat.get_memory_usage(), is_metrics_suspended: self.is_handle_request_disabled(), }; Ok(metrics) } pub fn is_handle_request_disabled(&self) -> bool { self.disable_handle_request .load(std::sync::atomic::Ordering::Relaxed) } pub fn get_free_disk_space(&self) -> u64 { utils::get_free_disk_space(&self.get_data_server_config().db_file) } pub fn get_overview(&self) -> Result> { let overview = OverviewData { server_name: smartdns::smartdns_get_server_name(), db_size: self.db.get_db_size(), startup_timestamp: self.startup_timestamp, free_disk_space: self.get_free_disk_space(), is_process_suspended: self.is_handle_request_disabled(), }; Ok(overview) } pub fn insert_client_by_list(&self, data: &Vec) -> Result<(), Box> { self.db.insert_client(data) } pub fn insert_domain_by_list(&self, data: &Vec) -> Result<(), Box> { self.db.insert_domain(data) } pub fn insert_domain(&self, data: &DomainData) -> Result<(), Box> { let list = vec![data.clone()]; self.stat.add_total_request(1); if data.is_blocked { self.stat.add_total_blocked_request(1); } if data.reply_code != 0 { self.stat.add_total_failed_request(1); } self.db.insert_domain(&list) } async fn data_server_handle_dns_request( this: Arc, req_list: &Vec>, ) { let mut domain_data_list = Vec::new(); let mut client_data_list = Vec::new(); let mut blocked_num = 0; let mut failed_num = 0; let timestamp_now = get_utc_time_ms(); // Pass 1: populate cache from incoming requests { let mut mac_cache = this.mac_cache.lock().unwrap(); if mac_cache.len() > 10000 { mac_cache.clear(); } for req in req_list { let mac_str = req .get_remote_mac() .iter() .map(|byte| format!("{:02x}", byte)) .collect::>() .join(":"); if mac_str != "00:00:00:00:00:00" { mac_cache.insert(req.get_remote_addr(), mac_str); } } } for req in req_list { if req.is_prefetch_request() { continue; } if req.is_dualstack_request() { continue; } if req.get_is_blocked() { blocked_num += 1; } if req.get_rcode() != 0 { failed_num += 1; } let domain_data = DomainData { id: 0, domain: req.get_domain(), domain_type: req.get_qtype(), client: req.get_remote_addr(), domain_group: req.get_group_name(), reply_code: req.get_rcode(), timestamp: req.get_query_timestamp(), query_time: req.get_query_time(), ping_time: req.get_ping_time(), is_blocked: req.get_is_blocked(), is_cached: req.get_is_cached(), }; dns_log!( LogLevel::DEBUG, "insert domain:{}, type:{}", domain_data.domain, domain_data.domain_type ); domain_data_list.push(domain_data); let client_ip = req.get_remote_addr(); let mut mac_str = req .get_remote_mac() .iter() .map(|byte| format!("{:02x}", byte)) .collect::>() .join(":"); if mac_str == "00:00:00:00:00:00" { if let Some(cached_mac) = this.mac_cache.lock().unwrap().get(&client_ip) { mac_str = cached_mac.clone(); } } let mut pending = this.client_pending_list.lock().unwrap(); if let Some(existing) = pending.get_mut(&client_ip) { if mac_str != "00:00:00:00:00:00" { existing.1.mac = mac_str; } existing.1.last_query_timestamp = timestamp_now; } else { let client_data = ClientData { id: 0, client_ip: client_ip.clone(), hostname: "".to_string(), mac: mac_str, last_query_timestamp: timestamp_now, }; pending.insert(client_ip, (timestamp_now, client_data)); } } { let mut pending = this.client_pending_list.lock().unwrap(); let mut to_remove = Vec::new(); for (ip, (first_seen, data)) in pending.iter() { if timestamp_now - *first_seen > 3000 || data.mac != "00:00:00:00:00:00" { to_remove.push(ip.clone()); } } for ip in to_remove { if let Some((_, data)) = pending.remove(&ip) { client_data_list.push(data); } } } this.stat.add_total_request(domain_data_list.len() as u64); this.stat.add_total_blocked_request(blocked_num as u64); this.stat.add_total_failed_request(failed_num as u64); dns_log!( LogLevel::DEBUG, "insert domain list count:{}", domain_data_list.len() ); let ret = DataServer::call_blocking(this.clone(), move || { let _ = match this.insert_domain_by_list(&domain_data_list) { Ok(v) => v, Err(e) => return Err(e.to_string()), }; let ret = match this.insert_client_by_list(&client_data_list) { Ok(v) => v, Err(e) => return Err(e.to_string()), }; Ok(ret) }) .await; if let Err(e) = ret { dns_log!(LogLevel::ERROR, "insert domain error: {}", e); return; } let ret = ret.unwrap(); if let Err(e) = ret { dns_log!(LogLevel::ERROR, "insert domain error: {}", e); } } pub async fn get_log_stream(&self) -> mpsc::Receiver { return self.server_log.get_log_stream().await; } pub fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32) { self.server_log.dispatch_log(level, msg, msg_len); } pub async fn get_audit_log_stream(&self) -> mpsc::Receiver { return self.server_audit_log.get_audit_log_stream().await; } pub fn server_audit_log(&self, msg: &str, msg_len: i32) { self.server_audit_log.dispatch_audit_log(msg, msg_len); } fn server_check(&self) { let free_disk_space = self.get_free_disk_space(); if free_disk_space < MIN_FREE_DISK_SPACE { if self .disable_handle_request .fetch_or(true, std::sync::atomic::Ordering::Relaxed) { return; } dns_log!( LogLevel::WARN, "free disk space is low, stop handle request. {}", self.disable_handle_request .load(std::sync::atomic::Ordering::Relaxed) ); } else { if !self .disable_handle_request .load(std::sync::atomic::Ordering::Relaxed) { return; } self.disable_handle_request .store(false, std::sync::atomic::Ordering::Relaxed); dns_log!( LogLevel::INFO, "free disk space is enough, start handle request." ); } } async fn data_server_loop(this: Arc) -> Result<(), Box> { let mut rx: mpsc::Receiver<()>; let mut data_rx: mpsc::Receiver>; let batch_mode = *this.recv_in_batch.lock().unwrap(); { let mut _rx = this.notify_rx.lock().unwrap(); rx = _rx.take().unwrap(); let mut _rx = this.data_rx.lock().unwrap(); data_rx = _rx.take().unwrap(); } this.stat.clone().start_worker()?; let req_list_size = if batch_mode { 1024 * 32 } else { 1 }; let mut req_list: Vec> = Vec::with_capacity(req_list_size); let batch_size = if batch_mode { 1024 * 8 } else { 1 }; let mut recv_buffer = Vec::with_capacity(batch_size); let mut batch_timer: Option = None; let mut check_timer = tokio::time::interval(Duration::from_secs(60)); let mut client_flush_timer = tokio::time::interval(Duration::from_secs(10)); let is_check_timer_running = Arc::new(AtomicBool::new(false)); dns_log!(LogLevel::DEBUG, "data server start."); loop { tokio::select! { _ = rx.recv() => { break; } _ = client_flush_timer.tick() => { let mut flush_list = Vec::new(); let timestamp_now = get_utc_time_ms(); { let mut pending = this.client_pending_list.lock().unwrap(); let mut to_remove = Vec::new(); for (ip, (first_seen, data)) in pending.iter() { if timestamp_now - *first_seen > 10000 || data.mac != "00:00:00:00:00:00" { to_remove.push(ip.clone()); } } for ip in to_remove { if let Some((_, data)) = pending.remove(&ip) { flush_list.push(data); } } } if !flush_list.is_empty() { let this_clone = this.clone(); let _ = DataServer::call_blocking(this.clone(), move || { let _ = this_clone.insert_client_by_list(&flush_list); Ok::<(), String>(()) }).await; } } _ = check_timer.tick() => { if is_check_timer_running.fetch_xor(true, std::sync::atomic::Ordering::Relaxed) { continue; } let is_check_timer_running_clone = is_check_timer_running.clone(); let this_clone = this.clone(); let ret = DataServer::call_blocking(this.clone(), move || { this_clone.server_check(); is_check_timer_running_clone.store(false, std::sync::atomic::Ordering::Relaxed); }).await; if let Err(e) = ret { dns_log!(LogLevel::WARN, "data server check error: {}", e); } } _ = async { if let Some(ref mut timer) = batch_timer { timer.tick().await; } }, if batch_timer.is_some() => { batch_timer = None; DataServer::data_server_handle_dns_request(this.clone(), &req_list).await; req_list.clear(); } count = data_rx.recv_many(&mut recv_buffer, batch_size) => { if count <= 0 { continue; } req_list.extend(recv_buffer.drain(0..count)); if batch_mode { if req_list.len() >= 1 && batch_timer.is_none() { let fill = (req_list.len() as f32 / batch_size as f32) .max(0.0) .min(1.0); let delay_ms = (1000.0 - 990.0 * fill) as u64; batch_timer = Some(tokio::time::interval_at( Instant::now() + Duration::from_millis(delay_ms), Duration::from_secs(2), )); } if req_list.len() < batch_size { continue; } } batch_timer = None; DataServer::data_server_handle_dns_request(this.clone(), &req_list).await; req_list.clear(); } } } { let mut flush_list = Vec::new(); { let mut pending = this.client_pending_list.lock().unwrap(); for (_, (_, data)) in pending.drain() { flush_list.push(data); } } if !flush_list.is_empty() { let this_clone = this.clone(); let _ = DataServer::call_blocking(this.clone(), move || { let _ = this_clone.insert_client_by_list(&flush_list); Ok::<(), String>(()) }).await; } } this.stat.clone().stop_worker(); Ok(()) } fn stop_data_server(&self) { if let Some(tx) = self.notify_tx.as_ref().cloned() { let plugin = match self.get_plugin() { Ok(plugin) => plugin, Err(e) => { dns_log!(LogLevel::ERROR, "get plugin error: {}", e); return; } }; let rt = plugin.get_runtime(); tokio::task::block_in_place(|| { let _ = rt.block_on(async { let _ = tx.send(()).await; }); }); } } async fn call_blocking( this: Arc, func: F, ) -> Result> where F: FnOnce() -> R + Send + 'static, R: Send + 'static, { let rt = this.get_plugin().unwrap().get_runtime(); let ret = rt.spawn_blocking(move || -> R { return func(); }); let ret = ret.await; if ret.is_err() { return Err(Box::new(ret.err().unwrap())); } let ret = ret.unwrap(); return Ok(ret); } } ================================================ FILE: plugin/smartdns-ui/src/data_stats.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 std::{ collections::HashMap, error::Error, sync::{atomic::AtomicU32, RwLock}, }; use crate::{data_server::DataServerConfig, db::*, dns_log, smartdns::*, utils}; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }; #[cfg(target_has_atomic = "64")] use std::sync::atomic::AtomicU64; use std::time::Duration; use tokio::sync::mpsc; use tokio::time::{interval_at, Instant}; #[cfg(target_has_atomic = "64")] struct DataStatsItem { total_request: AtomicU64, total_blocked_request: AtomicU64, total_failed_request: AtomicU64, qps: AtomicU32, qps_count: AtomicU32, request_dropped: AtomicU64, } #[cfg(not(target_has_atomic = "64"))] struct DataStatsItem { total_request: Arc>, total_blocked_request: Arc>, total_failed_request: Arc>, qps: AtomicU32, qps_count: AtomicU32, request_dropped: Arc>, } impl DataStatsItem { pub fn new() -> Self { #[cfg(target_has_atomic = "64")] let ret = DataStatsItem { total_request: 0.into(), total_blocked_request: 0.into(), total_failed_request: 0.into(), qps: 0.into(), qps_count: 0.into(), request_dropped: 0.into(), }; #[cfg(not(target_has_atomic = "64"))] let ret = DataStatsItem { total_request: Arc::new(Mutex::new(0)), total_blocked_request: Arc::new(Mutex::new(0)), total_failed_request: Arc::new(Mutex::new(0)), qps: 0.into(), qps_count: 0.into(), request_dropped: Arc::new(Mutex::new(0)), }; return ret; } pub fn get_qps(&self) -> u32 { return self.qps.load(Ordering::Relaxed); } pub fn add_qps_count(&self, count: u32) { self.qps_count.fetch_add(count, Ordering::Relaxed); } pub fn update_qps(&self) { let qps = self.qps_count.fetch_and(0, Ordering::Relaxed); self.qps.store(qps, Ordering::Relaxed); } pub fn add_request_drop(&self, count: u64) { #[cfg(target_has_atomic = "64")] { self.request_dropped.fetch_and(count, Ordering::Relaxed); } #[cfg(not(target_has_atomic = "64"))] { let mut dropped = self.request_dropped.lock().unwrap(); *dropped += count; } } pub fn get_request_drop(&self) -> u64 { #[cfg(target_has_atomic = "64")] { return self.request_dropped.load(Ordering::Relaxed); } #[cfg(not(target_has_atomic = "64"))] { let dropped = self.request_dropped.lock().unwrap(); return *dropped; } } pub fn get_total_request(&self) -> u64 { #[cfg(target_has_atomic = "64")] { return self.total_request.load(Ordering::Relaxed); } #[cfg(not(target_has_atomic = "64"))] { let total = self.total_request.lock().unwrap(); return *total; } } pub fn add_total_request(&self, total: u64) { #[cfg(target_has_atomic = "64")] { self.total_request.fetch_add(total, Ordering::Relaxed); } #[cfg(not(target_has_atomic = "64"))] { let mut total_request = self.total_request.lock().unwrap(); *total_request += total; } } pub fn get_total_blocked_request(&self) -> u64 { #[cfg(target_has_atomic = "64")] { return self.total_blocked_request.load(Ordering::Relaxed); } #[cfg(not(target_has_atomic = "64"))] { let total = self.total_blocked_request.lock().unwrap(); return *total; } } pub fn add_total_blocked_request(&self, total: u64) { #[cfg(target_has_atomic = "64")] { self.total_blocked_request .fetch_add(total, Ordering::Relaxed); } #[cfg(not(target_has_atomic = "64"))] { let mut total_blocked_request = self.total_blocked_request.lock().unwrap(); *total_blocked_request += total; } } pub fn add_total_failed_request(&self, total: u64) { #[cfg(target_has_atomic = "64")] { self.total_failed_request .fetch_add(total, Ordering::Relaxed); } #[cfg(not(target_has_atomic = "64"))] { let mut total_failed_request = self.total_failed_request.lock().unwrap(); *total_failed_request += total; } } pub fn get_total_failed_request(&self) -> u64 { #[cfg(target_has_atomic = "64")] { return self.total_failed_request.load(Ordering::Relaxed); } #[cfg(not(target_has_atomic = "64"))] { let total = self.total_failed_request.lock().unwrap(); return *total; } } #[allow(dead_code)] pub fn get_current_hour_total(&self) -> u64 { return Stats::get_request_total(); } } pub struct DataStats { task: Mutex>>, notify_tx: Option>, notify_rx: Mutex>>, is_run: AtomicBool, data: DataStatsItem, db: Arc, conf: Arc>, is_hourly_work_running: AtomicBool, } impl DataStats { pub fn new(db: Arc, conf: Arc>) -> Arc { let (tx, rx) = mpsc::channel(100); Arc::new(DataStats { task: Mutex::new(None), notify_rx: Mutex::new(Some(rx)), notify_tx: Some(tx), is_run: AtomicBool::new(false), data: DataStatsItem::new(), db: db, conf: conf, is_hourly_work_running: AtomicBool::new(false), }) } pub fn get_qps(&self) -> u32 { return self.data.get_qps(); } pub fn add_qps_count(&self, count: u32) { self.data.add_qps_count(count); } pub fn update_qps(&self) { self.data.update_qps(); } pub fn add_request_drop(&self, count: u64) { self.data.add_request_drop(count); } pub fn get_request_drop(&self) -> u64 { return self.data.get_request_drop(); } pub fn get_total_blocked_request(&self) -> u64 { return self.data.get_total_blocked_request(); } pub fn add_total_blocked_request(&self, total: u64) { self.data.add_total_blocked_request(total); } pub fn get_total_failed_request(&self) -> u64 { return self.data.get_total_failed_request(); } pub fn add_total_failed_request(&self, total: u64) { self.data.add_total_failed_request(total); } pub fn get_total_request(&self) -> u64 { return self.data.get_total_request(); } pub fn get_current_hour_total(&self) -> u64 { return self.data.get_current_hour_total(); } pub fn add_total_request(&self, total: u64) { self.data.add_total_request(total); } pub fn get_memory_usage(&self) -> u64 { let statm_path = "/proc/self/statm"; let statm = std::fs::read_to_string(statm_path); if let Err(_) = statm { return 0; } let statm = statm.unwrap(); let statm: Vec<&str> = statm.split_whitespace().collect(); if statm.len() < 2 { return 0; } let pages = statm[1].parse::(); if let Err(_) = pages { return 0; } let pages = pages.unwrap(); let pagesizie = utils::get_page_size() as u64; return pages * pagesizie; } pub fn init(self: &Arc) -> Result<(), Box> { dns_log!(LogLevel::DEBUG, "init data stats"); self.load_status_data()?; Ok(()) } pub fn load_status_data(self: &Arc) -> Result<(), Box> { let status_data = match self.db.get_status_data_list() { Ok(data) => data, Err(_) => HashMap::new(), }; // load total request count let mut total_count = 0 as u64; let status_data_total_count = status_data.get("total_request"); if status_data_total_count.is_some() { let count = status_data_total_count.unwrap().parse::(); if let Ok(count) = count { total_count = count; } else { total_count = 0; } } if total_count == 0 { let count = self.db.get_domain_list_count(None); total_count = count; } self.data.add_total_request(total_count); // load total blocked request let mut total_blocked_count = 0 as u64; let status_data_total_blocked_count = status_data.get("total_blocked_request"); if status_data_total_blocked_count.is_some() { let count = status_data_total_blocked_count.unwrap().parse::(); if let Ok(count) = count { total_blocked_count = count; } else { total_blocked_count = 0; } } if total_blocked_count == 0 { let mut parm = DomainListGetParam::new(); parm.is_blocked = Some(true); let count = self.db.get_domain_list_count(Some(&parm)); total_blocked_count = count; } self.data.add_total_blocked_request(total_blocked_count); // load request drop count let mut request_drop = 0 as u64; let status_data_request_drop = status_data.get("request_drop"); if status_data_request_drop.is_some() { let count = status_data_request_drop.unwrap().parse::(); if let Ok(count) = count { request_drop = count; } else { request_drop = 0; } } self.data.add_request_drop(request_drop); // load total failed request let mut total_failed_count = 0 as u64; let status_data_total_failed_count = status_data.get("total_failed_request"); if status_data_total_failed_count.is_some() { let count = status_data_total_failed_count.unwrap().parse::(); if let Ok(count) = count { total_failed_count = count; } else { total_failed_count = 0; } } self.data.add_total_failed_request(total_failed_count); Ok(()) } pub fn save_status_data(self: &Arc) -> Result<(), Box> { self.db.set_status_data( "total_request", self.get_total_request().to_string().as_str(), )?; self.db.set_status_data( "total_blocked_request", self.get_total_blocked_request().to_string().as_str(), )?; self.db.set_status_data( "total_failed_request", self.get_total_failed_request().to_string().as_str(), )?; self.db.set_status_data( "request_drop", self.get_request_drop().to_string().as_str(), )?; Ok(()) } pub fn start_worker(self: &Arc) -> Result<(), Box> { let this = self.clone(); let task = tokio::spawn(async move { DataStats::worker_loop(&this).await; }); *(self.task.lock().unwrap()) = Some(task); self.is_run.store(true, Ordering::Relaxed); Ok(()) } pub fn refresh(self: &Arc) { let now = get_utc_time_ms(); let ret = self .db .delete_domain_before_timestamp(now - self.conf.read().unwrap().max_log_age_ms as u64); if let Err(e) = ret { if e.to_string() == "Query returned no rows" { return; } dns_log!( LogLevel::WARN, "delete domain before timestamp error: {}", e ); } let ret = self.db.refresh_client_top_list(now - 7 * 24 * 3600 * 1000); if let Err(e) = ret { dns_log!(LogLevel::WARN, "refresh client top list error: {}", e); } let ret = self.db.refresh_domain_top_list(now - 7 * 24 * 3600 * 1000); if let Err(e) = ret { dns_log!(LogLevel::WARN, "refresh domain top list error: {}", e); } let _ = self .db .delete_hourly_query_count_before_timestamp(30 * 24 * 3600 * 1000); let _ = self .db .delete_daily_query_count_before_timestamp(90 * 24 * 3600 * 1000); } async fn update_stats(self: &Arc) { if self .is_hourly_work_running .fetch_or(true, Ordering::Acquire) { return; } let this = self.clone(); tokio::task::spawn_blocking(move || { this.refresh(); this.is_hourly_work_running.store(false, Ordering::Release); }); } async fn worker_loop(this: &Arc) { let mut rx: mpsc::Receiver<()>; { let mut _rx = this.notify_rx.lock().unwrap(); rx = _rx.take().unwrap(); } this.clone().update_stats().await; let start: Instant = Instant::now() + Duration::from_secs(utils::seconds_until_next_hour()); let mut hour_timer = interval_at(start, Duration::from_secs(60 * 60)); let mut second_timer = interval_at(Instant::now(), Duration::from_secs(1)); loop { tokio::select! { _ = rx.recv() => { break; } _ = second_timer.tick() => { this.update_qps(); } _ = hour_timer.tick() => { this.update_stats().await; } } } let ret = this.save_status_data(); if let Err(e) = ret { dns_log!(LogLevel::WARN, "save status data error: {}", e); } } pub fn stop_worker(&self) { if self.is_run.load(Ordering::Relaxed) == false { return; } if let Some(tx) = self.notify_tx.as_ref().cloned() { let _ = tx.try_send(()); } let mut task = self.task.lock().unwrap(); if let Some(task) = task.take() { tokio::task::block_in_place(|| { let _ = tokio::runtime::Handle::current().block_on(task); }); } self.is_run.store(false, Ordering::Relaxed); } } impl Drop for DataStats { fn drop(&mut self) { self.stop_worker(); } } ================================================ FILE: plugin/smartdns-ui/src/data_upstream_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::{smartdns, DnsServerType}; #[derive(Debug, Clone)] pub struct UpstreamServerInfo { pub host: String, pub ip: String, pub port: u16, pub server_type: DnsServerType, pub total_query_count: u64, pub total_query_success: u64, pub total_query_recv_count: u64, pub query_success_rate: f64, pub avg_time: f64, pub status: String, pub security: String, } impl UpstreamServerInfo { pub fn get_all() -> Result, Box> { let mut servers = Vec::new(); smartdns::DnsUpstreamServer::get_server_list()? .iter() .for_each(|server| { let stats = server.get_server_stats(); let status = if stats.get_query_total() == 0 { "Unknown" } else if server.is_server_alive() { "Normal" } else { "Abnormal" }; let security_status = server.get_server_security_status(); servers.push(UpstreamServerInfo { host: server.get_host(), ip: server.get_ip(), port: server.get_port(), server_type: server.get_type(), total_query_count: stats.get_query_total(), total_query_recv_count: stats.get_query_recv(), total_query_success: stats.get_query_success(), query_success_rate: stats.get_success_rate(), avg_time: stats.get_query_avg_time(), status: status.to_string(), security: security_status.to_string(), }); }); Ok(servers) } } ================================================ FILE: plugin/smartdns-ui/src/db.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::dns_log; use crate::smartdns; use crate::smartdns::*; use crate::utils; use std::collections::HashMap; use std::error::Error; use std::fs; use std::sync::Mutex; use std::vec; use chrono::Local; use rusqlite::Transaction; use rusqlite::{Connection, OpenFlags, Result}; pub struct DB { conn: Mutex>, version: i32, query_plan: bool, } #[derive(Debug, Clone)] pub struct ClientData { pub id: u32, pub hostname: String, pub client_ip: String, pub mac: String, pub last_query_timestamp: u64, } #[derive(Debug, Clone)] pub struct ClientQueryCount { pub client_ip: String, pub count: u32, pub timestamp_start: u64, pub timestamp_end: u64, } #[derive(Debug, Clone)] pub struct DomainQueryCount { pub domain: String, pub count: u32, pub timestamp_start: u64, pub timestamp_end: u64, } #[derive(Debug, Clone)] pub struct HourlyQueryCountItem { pub hour: String, pub query_count: u32, } #[derive(Debug, Clone)] pub struct HourlyQueryCount { pub query_timestamp: u64, pub hourly_query_count: Vec, } #[derive(Debug, Clone)] pub struct DailyQueryCountItem { pub day: String, pub query_count: u32, } #[derive(Debug, Clone)] pub struct DailyQueryCount { pub query_timestamp: u64, pub daily_query_count: Vec, } #[derive(Debug, Clone)] pub struct DomainData { pub id: u64, pub timestamp: u64, pub domain: String, pub domain_type: u32, pub client: String, pub domain_group: String, pub reply_code: u16, pub query_time: i32, pub ping_time: f64, pub is_blocked: bool, pub is_cached: bool, } #[derive(Debug, Clone)] pub struct QueryDomainListResult { pub domain_list: Vec, pub total_count: u64, pub step_by_cursor: bool, } #[derive(Debug, Clone)] pub struct DomainListGetParamCursor { pub id: Option, pub total_count: u64, pub direction: String, } #[derive(Debug, Clone)] pub struct QueryClientListResult { pub client_list: Vec, pub total_count: u64, pub step_by_cursor: bool, } #[derive(Debug, Clone)] pub struct ClientListGetParamCursor { pub id: Option, pub total_count: u64, pub direction: String, } #[derive(Debug, Clone)] pub struct ClientListGetParam { pub id: Option, pub order: Option, pub page_num: u64, pub page_size: u64, pub client_ip: Option, pub mac: Option, pub hostname: Option, pub timestamp_before: Option, pub timestamp_after: Option, pub cursor: Option, } impl ClientListGetParam { pub fn new() -> Self { ClientListGetParam { id: None, page_num: 1, order: None, page_size: 10, client_ip: None, mac: None, hostname: None, timestamp_before: None, timestamp_after: None, cursor: None, } } } #[derive(Debug, Clone)] pub struct DomainListGetParam { pub id: Option, pub order: Option, pub page_num: u64, pub page_size: u64, pub domain: Option, pub domain_filter_mode: Option, pub domain_type: Option, pub client: Option, pub domain_group: Option, pub reply_code: Option, pub timestamp_before: Option, pub timestamp_after: Option, pub is_blocked: Option, pub is_cached: Option, pub cursor: Option, } impl DomainListGetParam { pub fn new() -> Self { DomainListGetParam { id: None, page_num: 1, order: None, page_size: 10, domain: None, domain_filter_mode: None, domain_type: None, client: None, domain_group: None, reply_code: None, timestamp_before: None, timestamp_after: None, is_blocked: None, is_cached: None, cursor: None, } } } impl DB { pub fn new() -> Self { DB { conn: Mutex::new(None), version: 10000, /* x: major version, xx: minor version, xx: patch version */ query_plan: std::env::var("SMARTDNS_DEBUG_SQL").is_ok(), } } fn create_table(&self, conn: &Connection) -> Result<()> { conn.execute( "CREATE TABLE IF NOT EXISTS domain ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp BIGINT NOT NULL, domain TEXT NOT NULL, domain_type INTEGER NOT NULL, client TEXT NOT NULL, domain_group TEXT NOT NULL, reply_code INTEGER NOT NULL, query_time INTEGER NOT NULL, ping_time REAL NOT NULL, is_blocked INTEGER DEFAULT 0, is_cached INTEGER DEFAULT 0 )", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_domain_timestamp ON domain (timestamp)", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_domain_client ON domain (client)", [], )?; conn.execute( "CREATE TABLE IF NOT EXISTS domain_hourly_count ( timestamp BIGINT PRIMARY KEY, count INTEGER DEFAULT 0 );", [], )?; conn.execute( "CREATE TABLE IF NOT EXISTS domain_daily_count ( timestamp BIGINT PRIMARY KEY, count INTEGER DEFAULT 0 );", [], )?; conn.execute( "CREATE TABLE IF NOT EXISTS top_domain_list ( domain TEXT PRIMARY KEY, count INTEGER DEFAULT 0, timestamp_start BIGINT DEFAULT 0, timestamp_end BIGINT DEFAULT 0 );", [], )?; conn.execute( "CREATE TABLE IF NOT EXISTS top_client_list ( client TEXT PRIMARY KEY, count INTEGER DEFAULT 0, timestamp_start BIGINT DEFAULT 0, timestamp_end BIGINT DEFAULT 0 );", [], )?; conn.execute( " CREATE TABLE IF NOT EXISTS client ( id INTEGER PRIMARY KEY, client_ip TEXT NOT NULL, mac TEXT NOT NULL, hostname TEXT NOT NULL, last_query_timestamp BIGINT NOT NULL, UNIQUE(client_ip, mac) )", [], )?; conn.execute( "CREATE INDEX IF NOT EXISTS idx_client_last_query_timestamp ON client (last_query_timestamp)", [], )?; conn.execute( "CREATE TABLE IF NOT EXISTS config ( key TEXT PRIMARY KEY, value TEXT NOT NULL )", [], )?; conn.execute( "CREATE TABLE IF NOT EXISTS status_data ( key TEXT PRIMARY KEY, value TEXT NOT NULL )", [], )?; conn.execute( "INSERT INTO schema_version (version) VALUES (?)", [self.version], )?; Ok(()) } fn migrate_db(&self, _conn: &Connection) -> Result<(), Box> { return Err( "Currently Not Support Migrate Database, Please Backup DB File, And Restart Server." .into(), ); } fn init_db(&self, conn: &Connection) -> Result<(), Box> { conn.execute( "CREATE TABLE IF NOT EXISTS schema_version ( version INTEGER PRIMARY KEY )", [], )?; let current_version: i32 = conn .query_row( "SELECT version FROM schema_version ORDER BY version DESC LIMIT 1", [], |row| row.get(0), ) .unwrap_or(self.version); if current_version >= self.version { self.create_table(conn)?; } else { self.migrate_db(conn)?; } Ok(()) } pub fn open(&self, path: &str) -> Result<(), Box> { let ruconn: std::result::Result = Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE); let mut conn = self.conn.lock().unwrap(); if let Err(_) = ruconn { let ruconn = Connection::open_with_flags( path, OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE, )?; let ret = self.init_db(&ruconn); if let Err(e) = ret { _ = ruconn.close(); fs::remove_file(path)?; return Err(e); } *conn = Some(ruconn); } else { *conn = Some(ruconn.unwrap()); } conn.as_ref() .unwrap() .execute("PRAGMA synchronous = OFF", [])?; conn.as_ref() .unwrap() .execute("PRAGMA page_size = 4096", [])?; conn.as_ref() .unwrap() .execute("PRAGMA cache_size = -8192", [])?; conn.as_ref() .unwrap() .execute("PRAGMA temp_store = MEMORY", [])?; let current_auto_vacuum: i32 = conn.as_ref() .unwrap() .query_row("PRAGMA auto_vacuum", [], |row| row.get(0))?; dns_log!( LogLevel::DEBUG, "Current auto_vacuum: {}", current_auto_vacuum ); if current_auto_vacuum != 2 { dns_log!(LogLevel::INFO, "Set auto_vacuum to INCREMENTAL"); conn.as_ref() .unwrap() .execute("PRAGMA auto_vacuum = INCREMENTAL", [])?; conn.as_ref().unwrap().execute("VACUUM", [])?; } conn.as_ref() .unwrap() .query_row("PRAGMA journal_mode = WAL", [], |_| Ok(()))?; conn.as_ref() .unwrap() .query_row("PRAGMA wal_autocheckpoint = 1000", [], |_| Ok(()))?; Ok(()) } pub fn run_vacuum(&self, pages: Option) -> Result<(), Box> { let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); dns_log!(LogLevel::DEBUG, "Start incremental vacuum"); conn.query_row("PRAGMA wal_checkpoint(PASSIVE)", [], |_| Ok(()))?; let vacuum_sql = if let Some(pages) = pages { format!("PRAGMA incremental_vacuum({})", pages) } else { "PRAGMA incremental_vacuum".to_string() }; let mut stmt = conn.prepare(vacuum_sql.as_str())?; let mut _reclaimed_pages = 0; let rows = stmt.query_map([], |_row| Ok(())); if let Ok(rows) = rows { for _row in rows { _reclaimed_pages += 1; } } Ok(()) } pub fn set_config(&self, key: &str, value: &str) -> Result<(), Box> { let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let mut stmt = conn.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?1, ?2)")?; let ret = stmt.execute(&[&key, &value]); if let Err(e) = ret { return Err(Box::new(e)); } Ok(()) } pub fn get_config_list(&self) -> Result, Box> { let mut ret = HashMap::new(); let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let mut stmt = conn.prepare("SELECT key, value FROM config").unwrap(); let rows = stmt.query_map([], |row| { let key: String = row.get(0)?; let value: String = row.get(1)?; Ok((key, value)) }); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { ret.insert(row.0, row.1); } } } Ok(ret) } pub fn set_status_data(&self, key: &str, value: &str) -> Result<(), Box> { let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let mut stmt = conn.prepare("INSERT OR REPLACE INTO status_data (key, value) VALUES (?1, ?2)")?; let ret = stmt.execute(&[&key, &value]); if let Err(e) = ret { return Err(Box::new(e)); } Ok(()) } pub fn get_status_data_list(&self) -> Result, Box> { let mut ret = HashMap::new(); let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let stmt = conn.prepare("SELECT key, value FROM status_data"); if let Err(e) = stmt { return Err(Box::new(e)); } let mut stmt = stmt.unwrap(); let rows = stmt.query_map([], |row| { let key: String = row.get(0)?; let value: String = row.get(1)?; Ok((key, value)) }); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { ret.insert(row.0, row.1); } } } Ok(ret) } pub fn debug_query_plan(&self, conn: &Connection, sql: String, sql_param: &Vec) { if !self.query_plan { return; } let sqlplan = "EXPLAIN QUERY PLAN ".to_string() + &sql; let stmt = conn.prepare(sqlplan.as_str()); if let Err(e) = stmt { dns_log!(LogLevel::DEBUG, "query plan sql error: {}", e); return; } let mut stmt = stmt.unwrap(); let plan_rows = stmt.query_map(rusqlite::params_from_iter(sql_param.clone()), |row| { Ok(row.get::<_, String>(3)?) }); if let Err(e) = plan_rows { dns_log!(LogLevel::DEBUG, "query plan error: {}", e); return; } let plan_rows = plan_rows.unwrap(); dns_log!(LogLevel::NOTICE, "sql: {}", sql); for plan in plan_rows { if let Ok(plan) = plan { dns_log!(LogLevel::NOTICE, "plan: {}", plan); } } } pub fn get_config(&self, key: &str) -> Result, Box> { let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let mut stmt = conn .prepare("SELECT value FROM config WHERE key = ?") .unwrap(); let rows = stmt.query_map(&[&key], |row| Ok(row.get(0)?)); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { return Ok(Some(row)); } } } Ok(None) } pub fn update_domain_hourly_count( &self, tx: &Transaction<'_>, hourly_count: &HashMap, ) -> Result<(), Box> { let mut stmt = tx.prepare( "INSERT INTO domain_hourly_count (timestamp, count) VALUES ( ?1, ?2 ) ON CONFLICT(timestamp) DO UPDATE SET count = count + ?2;", )?; for (k, v) in hourly_count { stmt.execute(rusqlite::params![k, v])?; } stmt.finalize()?; Ok(()) } pub fn update_domain_daily_count( &self, tx: &Transaction<'_>, daily_count: &HashMap, ) -> Result<(), Box> { let mut stmt = tx.prepare( "INSERT INTO domain_daily_count (timestamp, count) VALUES ( ?1, ?2 ) ON CONFLICT(timestamp) DO UPDATE SET count = count + ?2;", )?; for (k, v) in daily_count { stmt.execute(rusqlite::params![k, v])?; } stmt.finalize()?; Ok(()) } pub fn insert_domain(&self, data: &Vec) -> Result<(), Box> { let local_offset = Local::now().offset().local_minus_utc(); let mut conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let mut hourly_count = HashMap::new(); let mut daily_count = HashMap::new(); let conn = conn.as_mut().unwrap(); let tx = conn.transaction()?; let mut stmt = tx.prepare( "INSERT INTO domain \ (timestamp, domain, domain_type, client, domain_group, reply_code, query_time, ping_time, is_blocked, is_cached) \ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)")?; for d in data { let ret = stmt.execute(rusqlite::params![ &d.timestamp.to_string(), &d.domain, &d.domain_type.to_string(), &d.client, &d.domain_group, &d.reply_code, &d.query_time, &d.ping_time, &(d.is_blocked as i32), &(d.is_cached as i32) ]); if let Err(e) = ret { stmt.finalize()?; tx.rollback()?; return Err(Box::new(e)); } let localtimestamp = d.timestamp + local_offset as u64 * 1000; let hour_timestamp = localtimestamp - localtimestamp % 3600000 - local_offset as u64 * 1000; let day_timestamp = localtimestamp - localtimestamp % 86400000 - local_offset as u64 * 1000; hourly_count .entry(hour_timestamp) .and_modify(|v| *v += 1) .or_insert(1); daily_count .entry(day_timestamp) .and_modify(|v| *v += 1) .or_insert(1); } stmt.finalize()?; self.update_domain_hourly_count(&tx, &hourly_count)?; self.update_domain_daily_count(&tx, &daily_count)?; tx.commit()?; Ok(()) } pub fn get_db_file_path(&self) -> Option { let conn = self.conn.lock().unwrap(); if conn.is_none() { return None; } let conn = conn.as_ref().unwrap(); conn.path().map(|v| v.to_string()) } pub fn get_readonly_conn(&self) -> Option { let conn = self.conn.lock().unwrap(); if conn.is_none() { return None; } let conn = conn.as_ref().unwrap(); let read_conn = Connection::open_with_flags( conn.path().unwrap(), OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX, ); if let Err(_) = read_conn { return None; } Some(read_conn.unwrap()) } /// # Returns /// /// A tuple containing: /// - `String`: The SQL WHERE clause. /// - `String`: The SQL ORDER BY clause. /// - `Vec`: The parameters for the SQL query. pub fn get_domain_sql_where( param: Option<&DomainListGetParam>, ) -> Result<(String, String, Vec), Box> { let mut is_desc_order = true; let mut is_cursor_prev = false; let param = match param { Some(v) => v, None => return Ok((String::new(), String::new(), Vec::new())), }; let mut order_timestamp_first = true; let mut cusor_with_timestamp = false; let mut sql_where = Vec::new(); let mut sql_param: Vec = Vec::new(); let mut sql_order = String::new(); if let Some(v) = ¶m.id { sql_where.push("id = ?".to_string()); sql_param.push(v.to_string()); order_timestamp_first = false; } if let Some(v) = ¶m.order { if v.eq_ignore_ascii_case("asc") { is_cursor_prev = true; } else if v.eq_ignore_ascii_case("desc") { is_cursor_prev = false; } else { return Err("order param error".into()); } } if let Some(v) = ¶m.cursor { if v.direction.eq_ignore_ascii_case("prev") { is_desc_order = !is_desc_order; } else if v.direction.eq_ignore_ascii_case("next") { // do nothing } else { return Err("cursor direction param error".into()); } } if let Some(v) = ¶m.domain { if let Some(m) = ¶m.domain_filter_mode { match m.as_str() { "endwith" => { sql_where.push("domain LIKE ?".to_string()); sql_param.push(format!("{}%", v)); } "startwith" => { sql_where.push("domain LIKE ?".to_string()); sql_param.push(format!("%{}", v)); } "contains" => { sql_where.push("domain LIKE ?".to_string()); sql_param.push(format!("%{}%", v)); } "equals" => { sql_where.push("domain = ?".to_string()); sql_param.push(v.to_string()); } _ => return Err("domain_filter_mode param error".into()), } } else { sql_where.push("domain = ?".to_string()); sql_param.push(v.to_string()); order_timestamp_first = false; } } if let Some(v) = ¶m.domain_type { sql_where.push("domain_type = ?".to_string()); sql_param.push(v.to_string()); order_timestamp_first = false; } if let Some(v) = ¶m.client { sql_where.push("client = ?".to_string()); sql_param.push(v.clone()); order_timestamp_first = false; } if let Some(v) = ¶m.domain_group { sql_where.push("domain_group = ?".to_string()); sql_param.push(v.clone()); order_timestamp_first = false; } if let Some(v) = ¶m.reply_code { sql_where.push("reply_code = ?".to_string()); sql_param.push(v.to_string()); order_timestamp_first = false; } if let Some(v) = ¶m.timestamp_before { let mut use_cursor = false; if param.cursor.is_some() && (is_desc_order || is_cursor_prev) { let v = param.cursor.as_ref().unwrap().id; if let Some(v) = v { sql_where.push("id < ?".to_string()); sql_param.push(v.to_string()); use_cursor = true; order_timestamp_first = false; cusor_with_timestamp = true; } } if use_cursor == false { sql_where.push("timestamp <= ?".to_string()); sql_param.push(v.to_string()); } } if let Some(v) = ¶m.timestamp_after { let mut use_cursor = false; if param.cursor.is_some() && (!is_desc_order || is_cursor_prev) { let v = param.cursor.as_ref().unwrap().id; if let Some(v) = v { sql_where.push("id > ?".to_string()); sql_param.push(v.to_string()); use_cursor = true; order_timestamp_first = false; cusor_with_timestamp = true; } } if use_cursor == false { sql_where.push("timestamp >= ?".to_string()); sql_param.push(v.to_string()); } } if !cusor_with_timestamp { if let Some(v) = ¶m.cursor { if is_cursor_prev { if let Some(id) = &v.id { if is_desc_order { sql_where.push("id > ?".to_string()); } else { sql_where.push("id < ?".to_string()); } sql_param.push(id.to_string()); order_timestamp_first = false; } } else { if let Some(id) = &v.id { if is_desc_order { sql_where.push("id < ?".to_string()); } else { sql_where.push("id > ?".to_string()); } sql_param.push(id.to_string()); order_timestamp_first = false; } } } } if let Some(v) = ¶m.is_blocked { if *v { sql_where.push("is_blocked = 1".to_string()); } else { sql_where.push("is_blocked = 0".to_string()); } order_timestamp_first = false; } if let Some(v) = ¶m.is_cached { if *v { sql_where.push("is_cached = 1".to_string()); } else { sql_where.push("is_cached = 0".to_string()); } order_timestamp_first = false; } if is_cursor_prev { is_desc_order = !is_desc_order; } if is_desc_order { if order_timestamp_first { sql_order.push_str(" ORDER BY timestamp DESC, id DESC"); } else { sql_order.push_str(" ORDER BY id DESC, timestamp DESC"); } } else { if order_timestamp_first { sql_order.push_str(" ORDER BY timestamp ASC, id ASC"); } else { sql_order.push_str(" ORDER BY id ASC, timestamp ASC"); } } let sql_where = if sql_where.is_empty() { String::new() } else { format!(" WHERE {}", sql_where.join(" AND ")) }; Ok((sql_where, sql_order, sql_param)) } pub fn get_domain_list_count(&self, param: Option<&DomainListGetParam>) -> u64 { let conn = self.get_readonly_conn(); if conn.as_ref().is_none() { return 0; } let conn = conn.as_ref().unwrap(); let mut sql = String::new(); let mut sql_param = Vec::new(); sql.push_str("SELECT COUNT(*) FROM domain"); if let Ok((sql_where, sql_order, mut ret_sql_param)) = Self::get_domain_sql_where(param) { sql.push_str(sql_where.as_str()); sql.push_str(sql_order.as_str()); sql_param.append(&mut ret_sql_param); } let mut stmt = conn.prepare(sql.as_str()).unwrap(); let rows = stmt.query_map(rusqlite::params_from_iter(sql_param), |row| Ok(row.get(0)?)); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { return row; } } } 0 } pub fn delete_domain_by_id(&self, id: u64) -> Result> { let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let ret = conn.execute("DELETE FROM domain WHERE id = ?", &[&id]); if let Err(e) = ret { return Err(Box::new(e)); } Ok(ret.unwrap() as u64) } pub fn delete_domain_before_timestamp(&self, timestamp: u64) -> Result> { let ret = { let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let ret = conn.execute("DELETE FROM domain WHERE timestamp <= ?", &[×tamp]); if let Err(e) = ret { return Err(Box::new(e)); } Ok(ret.unwrap() as u64) }; self.run_vacuum(Some(50000))?; ret } pub fn refresh_client_top_list(&self, timestamp: u64) -> Result<(), Box> { let mut client_count_list = Vec::new(); let conn = match self.get_readonly_conn() { Some(v) => v, None => return Err("db is not open".into()), }; let timestamp_now = smartdns::get_utc_time_ms(); let sql = "SELECT client, COUNT(*) FROM domain WHERE timestamp >= ? GROUP BY client ORDER BY COUNT(*) DESC LIMIT 20"; self.debug_query_plan(&conn, sql.to_string(), &vec![timestamp.to_string()]); let mut stmt = conn.prepare(sql)?; let rows = stmt.query_map([timestamp.to_string()], |row| { Ok(ClientQueryCount { client_ip: row.get(0)?, count: row.get(1)?, timestamp_start: timestamp, timestamp_end: timestamp_now, }) }); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { client_count_list.push(row); } } } let mut conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_mut().unwrap(); let tx = conn.transaction()?; let mut stmt = tx.prepare("DELETE FROM top_client_list")?; stmt.execute([])?; stmt.finalize()?; let mut stmt = tx.prepare("INSERT INTO top_client_list (client, count, timestamp_start, timestamp_end) VALUES ( ?1, ?2, $3, $4)")?; for client in &client_count_list { stmt.execute(rusqlite::params![ client.client_ip, client.count, client.timestamp_start, client.timestamp_end ])?; dns_log!( LogLevel::DEBUG, "client: {}, count: {}, timestamp_start: {}, timestamp_end: {}", client.client_ip, client.count, client.timestamp_start, client.timestamp_end ); } stmt.finalize()?; tx.commit()?; Ok(()) } pub fn get_client_top_list(&self, count: u32) -> Result, Box> { let mut ret = Vec::new(); let conn = self.get_readonly_conn(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let mut stmt = conn.prepare("SELECT client, count, timestamp_start, timestamp_end FROM top_client_list ORDER BY count DESC LIMIT ?")?; let rows = stmt.query_map([count.to_string()], |row| { Ok(ClientQueryCount { client_ip: row.get(0)?, count: row.get(1)?, timestamp_start: row.get(2)?, timestamp_end: row.get(3)?, }) }); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { ret.push(row); } } } Ok(ret) } pub fn delete_daily_query_count_before_timestamp( &self, timestamp: u64, ) -> Result> { let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let ret = conn.execute( "DELETE FROM domain_daily_count WHERE timestamp <= ?", &[×tamp], ); if let Err(e) = ret { return Err(Box::new(e)); } Ok(ret.unwrap() as u64) } pub fn get_daily_query_count(&self, past_days: u32) -> Result> { let mut ret = Vec::new(); let conn = self.get_readonly_conn(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let seconds = 86400 * past_days - utils::seconds_until_next_hour() as u32; let mut stmt = conn.prepare( "SELECT \ strftime('%Y-%m-%d', datetime(timestamp / 1000, 'unixepoch', 'localtime')) AS date, timestamp, count \ FROM \ domain_daily_count \ WHERE \ timestamp >= strftime('%s', 'now') * 1000 - ? * 1000 \ ORDER BY \ timestamp DESC;\ ", )?; let rows = stmt.query_map([seconds.to_string()], |row| { Ok(DailyQueryCountItem { day: row.get(0)?, query_count: row.get(2)?, }) }); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { ret.push(row); } } } Ok(DailyQueryCount { query_timestamp: smartdns::get_utc_time_ms(), daily_query_count: ret, }) } pub fn delete_hourly_query_count_before_timestamp( &self, timestamp: u64, ) -> Result> { let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let ret = conn.execute( "DELETE FROM domain_hourly_count WHERE timestamp <= ?", &[×tamp], ); if let Err(e) = ret { return Err(Box::new(e)); } Ok(ret.unwrap() as u64) } pub fn get_hourly_query_count( &self, past_hours: u32, ) -> Result> { let mut ret = Vec::new(); let conn = self.get_readonly_conn(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let query_start = std::time::Instant::now(); let conn = conn.as_ref().unwrap(); let seconds = 3600 * past_hours - utils::seconds_until_next_hour() as u32; let sql = "SELECT \ strftime('%Y-%m-%d %H:00:00', datetime(timestamp / 1000, 'unixepoch', 'localtime')) AS hour, timestamp, count \ FROM \ domain_hourly_count \ WHERE \ timestamp >= strftime('%s', 'now') * 1000 - ? * 1000 \ ORDER BY \ timestamp DESC;\ "; self.debug_query_plan(conn, sql.to_string(), &vec![seconds.to_string()]); let mut stmt = conn.prepare(sql)?; let rows = stmt.query_map([seconds.to_string()], |row| { Ok(HourlyQueryCountItem { hour: row.get(0)?, query_count: row.get(2)?, }) }); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { ret.push(row); } } } dns_log!( LogLevel::DEBUG, "hourly_query_count time: {}ms", query_start.elapsed().as_millis() ); Ok(HourlyQueryCount { query_timestamp: smartdns::get_utc_time_ms(), hourly_query_count: ret, }) } pub fn refresh_domain_top_list(&self, timestamp: u64) -> Result<(), Box> { let mut domain_count_list = Vec::new(); let conn = match self.get_readonly_conn() { Some(v) => v, None => return Err("db is not open".into()), }; let timestamp_now = smartdns::get_utc_time_ms(); let sql = "SELECT domain, COUNT(*) FROM domain WHERE timestamp >= ? GROUP BY domain ORDER BY COUNT(*) DESC LIMIT 20"; self.debug_query_plan(&conn, sql.to_string(), &vec![timestamp.to_string()]); let mut stmt = conn.prepare(sql)?; let rows = stmt.query_map([timestamp.to_string()], |row| { Ok(DomainQueryCount { domain: row.get(0)?, count: row.get(1)?, timestamp_start: timestamp, timestamp_end: timestamp_now, }) }); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { domain_count_list.push(row); } } } let mut conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_mut().unwrap(); let tx = conn.transaction()?; let mut stmt = tx.prepare("DELETE FROM top_domain_list")?; stmt.execute([])?; stmt.finalize()?; let mut stmt = tx.prepare("INSERT INTO top_domain_list (domain, count, timestamp_start, timestamp_end) VALUES ( ?1, ?2, ?3, ?4)")?; for domain in &domain_count_list { stmt.execute(rusqlite::params![ domain.domain, domain.count, domain.timestamp_start, domain.timestamp_end ])?; } stmt.finalize()?; tx.commit()?; Ok(()) } pub fn get_domain_top_list(&self, count: u32) -> Result, Box> { let mut ret = Vec::new(); let conn = self.get_readonly_conn(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let mut stmt = conn.prepare("SELECT domain, count, timestamp_start, timestamp_end FROM top_domain_list DESC LIMIT ?")?; let rows = stmt.query_map([count.to_string()], |row| { Ok(DomainQueryCount { domain: row.get(0)?, count: row.get(1)?, timestamp_start: row.get(2)?, timestamp_end: row.get(3)?, }) }); if let Err(e) = rows { return Err(Box::new(e)); } if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { ret.push(row); } } } Ok(ret) } pub fn get_domain_list( &self, param: Option<&DomainListGetParam>, ) -> Result> { let query_start = std::time::Instant::now(); let mut cursor_reverse = false; let mut ret = QueryDomainListResult { domain_list: vec![], total_count: 0, step_by_cursor: false, }; let conn = self.get_readonly_conn(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let (sql_where, sql_order, mut sql_param) = Self::get_domain_sql_where(param)?; let mut sql = String::new(); sql.push_str("SELECT id, timestamp, domain, domain_type, client, domain_group, reply_code, query_time, ping_time, is_blocked, is_cached FROM domain"); sql.push_str(sql_where.as_str()); sql.push_str(sql_order.as_str()); if let Some(p) = param { let mut with_offset = true; if let Some(cursor) = &p.cursor { if cursor.id.is_some() { sql.push_str(" LIMIT ?"); sql_param.push(p.page_size.to_string()); with_offset = false; } if cursor.direction.eq_ignore_ascii_case("prev") { cursor_reverse = true; } } if with_offset { sql.push_str(" LIMIT ? OFFSET ?"); sql_param.push(p.page_size.to_string()); sql_param.push(((p.page_num - 1) * p.page_size).to_string()); } } self.debug_query_plan(conn, sql.clone(), &sql_param); let stmt = conn.prepare(&sql); if let Err(e) = stmt { dns_log!(LogLevel::ERROR, "get_domain_list error: {}", e); return Err("get_domain_list error".into()); } let mut stmt = stmt?; let rows = stmt.query_map(rusqlite::params_from_iter(sql_param), |row| { Ok(DomainData { id: row.get(0)?, timestamp: row.get(1)?, domain: row.get(2)?, domain_type: row.get(3)?, client: row.get(4)?, domain_group: row.get(5)?, reply_code: row.get(6)?, query_time: row.get(7)?, ping_time: row.get(8)?, is_blocked: row.get(9)?, is_cached: row.get(10)?, }) }); if let Err(e) = rows { return Err(Box::new(e)); } if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { ret.domain_list.push(row); } } } if cursor_reverse { ret.domain_list.reverse(); } if let Some(p) = param { if let Some(v) = &p.cursor { ret.total_count = v.total_count; ret.step_by_cursor = true; } else { let total_count = self.get_domain_list_count(param); ret.total_count = total_count; } } dns_log!( LogLevel::DEBUG, "domain_list time: {}ms", query_start.elapsed().as_millis() ); Ok(ret) } pub fn insert_client(&self, client_data: &Vec) -> Result<(), Box> { let mut conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_mut().unwrap(); let tx = conn.transaction()?; let mut stmt = tx.prepare("INSERT INTO client (id, client_ip, mac, hostname, last_query_timestamp) VALUES ( (SELECT MAX(rowid) FROM client) + 1, ?1, ?2, ?3, ?4) ON CONFLICT(client_ip, mac) DO UPDATE SET last_query_timestamp = excluded.last_query_timestamp; ")?; for d in client_data { let ret = stmt.execute(rusqlite::params![ d.client_ip, d.mac, d.hostname, d.last_query_timestamp ]); if let Err(e) = ret { stmt.finalize()?; tx.rollback()?; return Err(Box::new(e)); } } stmt.finalize()?; tx.commit()?; Ok(()) } pub fn get_client_list_count(&self, param: Option<&ClientListGetParam>) -> u64 { let conn = self.get_readonly_conn(); if conn.as_ref().is_none() { return 0; } let conn = conn.as_ref().unwrap(); let mut sql = String::new(); let mut sql_param = Vec::new(); sql.push_str("SELECT COUNT(*) FROM client"); if let Ok((sql_where, sql_order, mut ret_sql_param)) = Self::get_client_sql_where(param) { sql.push_str(sql_where.as_str()); sql.push_str(sql_order.as_str()); sql_param.append(&mut ret_sql_param); } let mut stmt = conn.prepare(sql.as_str()).unwrap(); let rows = stmt.query_map(rusqlite::params_from_iter(sql_param), |row| Ok(row.get(0)?)); if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { return row; } } } 0 } fn get_client_sql_where( param: Option<&ClientListGetParam>, ) -> Result<(String, String, Vec), Box> { let mut is_desc_order = true; let mut is_cursor_prev = false; let param = match param { Some(v) => v, None => return Ok((String::new(), String::new(), Vec::new())), }; let mut order_timestamp_first = false; let mut cusor_with_timestamp = false; let mut sql_where = Vec::new(); let mut sql_param: Vec = Vec::new(); let mut sql_order = String::new(); if let Some(v) = ¶m.id { sql_where.push("id = ?".to_string()); sql_param.push(v.to_string()); order_timestamp_first = false; } if let Some(v) = ¶m.order { if v.eq_ignore_ascii_case("asc") { is_cursor_prev = true; } else if v.eq_ignore_ascii_case("desc") { is_cursor_prev = false; } else { return Err("order param error".into()); } } if let Some(v) = ¶m.cursor { if v.direction.eq_ignore_ascii_case("prev") { is_desc_order = !is_desc_order; } else if v.direction.eq_ignore_ascii_case("next") { // do nothing } else { return Err("cursor direction param error".into()); } } if let Some(v) = ¶m.client_ip { sql_where.push("client_ip = ?".to_string()); sql_param.push(v.to_string()); } if let Some(v) = ¶m.mac { sql_where.push("mac = ?".to_string()); sql_param.push(v.to_string()); } if let Some(v) = ¶m.hostname { sql_where.push("hostname = ?".to_string()); sql_param.push(v.to_string()); } if let Some(v) = ¶m.timestamp_before { let mut use_cursor = false; if param.cursor.is_some() && (is_desc_order || is_cursor_prev) { let v = param.cursor.as_ref().unwrap().id; if let Some(v) = v { sql_where.push("id < ?".to_string()); sql_param.push(v.to_string()); use_cursor = true; order_timestamp_first = false; cusor_with_timestamp = true; } } if use_cursor == false { sql_where.push("last_query_timestamp <= ?".to_string()); sql_param.push(v.to_string()); } } if let Some(v) = ¶m.timestamp_after { let mut use_cursor = false; if param.cursor.is_some() && (!is_desc_order || is_cursor_prev) { let v = param.cursor.as_ref().unwrap().id; if let Some(v) = v { sql_where.push("id > ?".to_string()); sql_param.push(v.to_string()); use_cursor = true; order_timestamp_first = false; cusor_with_timestamp = true; } } if use_cursor == false { sql_where.push("last_query_timestamp >= ?".to_string()); sql_param.push(v.to_string()); } } if !cusor_with_timestamp { if let Some(v) = ¶m.cursor { if is_cursor_prev { if let Some(id) = &v.id { if is_desc_order { sql_where.push("id > ?".to_string()); } else { sql_where.push("id < ?".to_string()); } sql_param.push(id.to_string()); order_timestamp_first = false; } } else { if let Some(id) = &v.id { if is_desc_order { sql_where.push("id < ?".to_string()); } else { sql_where.push("id > ?".to_string()); } sql_param.push(id.to_string()); order_timestamp_first = false; } } } } if is_desc_order { if order_timestamp_first { sql_order.push_str(" ORDER BY last_query_timestamp DESC, id DESC"); } else { sql_order.push_str(" ORDER BY id DESC, last_query_timestamp DESC"); } } else { if order_timestamp_first { sql_order.push_str(" ORDER BY last_query_timestamp ASC, id ASC"); } else { sql_order.push_str(" ORDER BY id ASC, last_query_timestamp ASC"); } } let sql_where = if sql_where.is_empty() { String::new() } else { format!(" WHERE {}", sql_where.join(" AND ")) }; Ok((sql_where, sql_order, sql_param)) } pub fn get_client_list( &self, param: Option<&ClientListGetParam>, ) -> Result> { let query_start = std::time::Instant::now(); let mut cursor_reverse = false; let mut ret = QueryClientListResult { client_list: vec![], total_count: 0, step_by_cursor: false, }; let conn = self.get_readonly_conn(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let (sql_where, sql_order, mut sql_param) = Self::get_client_sql_where(param)?; let mut sql = String::new(); sql.push_str("SELECT id, client_ip, mac, hostname, last_query_timestamp FROM client"); sql.push_str(sql_where.as_str()); sql.push_str(sql_order.as_str()); if let Some(p) = param { let mut with_offset = true; if let Some(cursor) = &p.cursor { if cursor.id.is_some() { sql.push_str(" LIMIT ?"); sql_param.push(p.page_size.to_string()); with_offset = false; } if cursor.direction.eq_ignore_ascii_case("prev") { cursor_reverse = true; } } if with_offset { sql.push_str(" LIMIT ? OFFSET ?"); sql_param.push(p.page_size.to_string()); sql_param.push(((p.page_num - 1) * p.page_size).to_string()); } } self.debug_query_plan(conn, sql.clone(), &sql_param); let stmt = conn.prepare(&sql); if let Err(e) = stmt { dns_log!(LogLevel::ERROR, "get_client_list error: {}", e); return Err("get_client_list error".into()); } let mut stmt = stmt?; let rows = stmt.query_map(rusqlite::params_from_iter(sql_param), |row| { Ok(ClientData { id: row.get(0)?, client_ip: row.get(1)?, mac: row.get(2)?, hostname: row.get(3)?, last_query_timestamp: row.get(4)?, }) }); if let Err(e) = rows { return Err(Box::new(e)); } if let Ok(rows) = rows { for row in rows { if let Ok(row) = row { ret.client_list.push(row); } } } if cursor_reverse { ret.client_list.reverse(); } if let Some(p) = param { if let Some(v) = &p.cursor { ret.total_count = v.total_count; ret.step_by_cursor = true; } else { let total_count = self.get_client_list_count(param); ret.total_count = total_count; } } dns_log!( LogLevel::DEBUG, "domain_list time: {}ms", query_start.elapsed().as_millis() ); Ok(ret) } pub fn delete_client_by_id(&self, id: u64) -> Result> { let conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return Err("db is not open".into()); } let conn = conn.as_ref().unwrap(); let ret = conn.execute("DELETE FROM client WHERE id = ?", &[&id]); if let Err(e) = ret { return Err(Box::new(e)); } Ok(ret.unwrap() as u64) } pub fn get_db_size(&self) -> u64 { let db_file = self.get_db_file_path(); let mut total_size = 0; if db_file.is_none() { return 0; } let db_file = db_file.unwrap(); let wal_file = db_file.clone() + "-wal"; let metadata = fs::metadata(db_file); if let Err(_) = metadata { return 0; } total_size += metadata.unwrap().len(); let wal_metadata = fs::metadata(wal_file); if let Ok(wal_metadata) = wal_metadata { let wal_size = wal_metadata.len(); total_size += wal_size; } total_size } pub fn close(&self) { let mut conn = self.conn.lock().unwrap(); if conn.as_ref().is_none() { return; } if let Some(t) = conn.take() { let _ = t.close(); } } } impl Drop for DB { fn drop(&mut self) { self.close(); } } ================================================ FILE: plugin/smartdns-ui/src/http_api_msg.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_server::*; use crate::data_upstream_server::UpstreamServerInfo; use crate::db::*; use crate::smartdns::LogLevel; use crate::whois::WhoIsInfo; use serde_json::json; use std::collections::HashMap; use std::error::Error; #[derive(Debug)] pub struct AuthUser { pub username: String, pub password: String, } #[derive(Debug)] pub struct TokenResponse { pub token: String, pub expires_in: String, } pub fn api_msg_parse_auth(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let username = v["username"].as_str(); if username.is_none() { return Err("username not found".into()); } let password = v["password"].as_str(); if password.is_none() { return Err("password not found".into()); } Ok(AuthUser { username: username.unwrap().to_string(), password: password.unwrap().to_string(), }) } pub fn api_msg_parse_auth_password_change(data: &str) -> Result<(String, String), Box> { let v: serde_json::Value = serde_json::from_str(data)?; let old_password = v["old_password"].as_str(); if old_password.is_none() { return Err("old_password not found".into()); } let password = v["password"].as_str(); if password.is_none() { return Err("password not found".into()); } Ok(( old_password.unwrap().to_string(), password.unwrap().to_string(), )) } pub fn api_msg_gen_auth_password_change(old_password: &str, password: &str) -> String { let json_str = json!({ "old_password": old_password, "password": password, }); json_str.to_string() } pub fn api_msg_gen_auth_login(auth: &AuthUser) -> String { let json_str = json!({ "username": auth.username, "password": auth.password, }); json_str.to_string() } pub fn api_msg_parse_count(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let count = v["count"].as_i64(); if count.is_none() { return Err("count not found".into()); } Ok(count.unwrap()) } pub fn api_msg_gen_count(count: i64) -> String { let json_str = json!({ "count": count, }); json_str.to_string() } pub fn api_msg_parse_json_object_domain_value( data: &serde_json::Value, ) -> Result> { let id = data["id"].as_u64(); if id.is_none() { return Err("id not found".into()); } let timestamp = data["timestamp"].as_u64(); if timestamp.is_none() { return Err("timestamp not found".into()); } let domain = data["domain"].as_str(); if domain.is_none() { return Err("domain not found".into()); } let domain_type = data["domain_type"].as_u64(); if domain_type.is_none() { return Err("domain_type not found".into()); } let client = data["client"].as_str(); if client.is_none() { return Err("client not found".into()); } let domain_group = data["domain_group"].as_str(); if domain_group.is_none() { return Err("domain_group not found".into()); } let reply_code = data["reply_code"].as_u64(); if reply_code.is_none() { return Err("reply_code not found".into()); } let query_time = data["query_time"].as_i64(); if query_time.is_none() { return Err("query_time not found".into()); } let ping_time = data["ping_time"].as_f64(); if ping_time.is_none() { return Err("ping_time not found".into()); } let is_blocked = data["is_blocked"].as_bool(); if is_blocked.is_none() { return Err("is_blocked not found".into()); } let is_cached = data["is_cached"].as_bool(); if is_cached.is_none() { return Err("is_cached not found".into()); } Ok(DomainData { id: id.unwrap(), timestamp: timestamp.unwrap(), domain: domain.unwrap().to_string(), domain_type: domain_type.unwrap() as u32, client: client.unwrap().to_string(), domain_group: domain_group.unwrap().to_string(), reply_code: reply_code.unwrap() as u16, query_time: query_time.unwrap() as i32, ping_time: ping_time.unwrap(), is_blocked: is_blocked.unwrap(), is_cached: is_cached.unwrap(), }) } pub fn api_msg_parse_domain(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; api_msg_parse_json_object_domain_value(&v) } pub fn api_msg_gen_json_object_domain(domain: &DomainData) -> serde_json::Value { json!({ "id": domain.id, "timestamp": domain.timestamp, "domain": domain.domain, "domain_type": domain.domain_type, "client": domain.client, "domain_group": domain.domain_group, "reply_code": domain.reply_code, "query_time": domain.query_time, "ping_time": domain.ping_time, "is_blocked": domain.is_blocked, "is_cached": domain.is_cached, }) } pub fn api_msg_gen_domain(domain: &DomainData) -> String { let json_str = api_msg_gen_json_object_domain(domain); json_str.to_string() } pub fn api_msg_parse_domain_list(data: &str) -> Result, Box> { let v: serde_json::Value = serde_json::from_str(data)?; let list_count = v["list_count"].as_u64(); if list_count.is_none() { return Err("list_count not found".into()); } let list_count = list_count.unwrap(); let mut domain_list = Vec::new(); for i in 0..list_count { let domain_object = &v["domain_list"][i as usize]; let domain_data = api_msg_parse_json_object_domain_value(domain_object)?; domain_list.push(domain_data); } Ok(domain_list) } pub fn api_msg_gen_domain_list( domain_list_result: &QueryDomainListResult, total_page: u64, total_count: u64, ) -> String { let json_str = json!({ "list_count": domain_list_result.domain_list.len(), "total_page": total_page, "total_count": total_count, "step_by_cursor": domain_list_result.step_by_cursor, "domain_list": domain_list_result.domain_list .iter() .map(|x| { api_msg_gen_json_object_domain(x) }) .collect::>() }); json_str.to_string() } pub fn api_msg_parse_client_list(data: &str) -> Result, Box> { let v: serde_json::Value = serde_json::from_str(data)?; let list_count = v["list_count"].as_u64(); if list_count.is_none() { return Err("list_count not found".into()); } let list_count = list_count.unwrap(); let mut client_list = Vec::new(); for i in 0..list_count { let client_object = &v["client_list"][i as usize]; let id = client_object["id"].as_u64(); if id.is_none() { return Err("id not found".into()); } let client_ip = client_object["client_ip"].as_str(); if client_ip.is_none() { return Err("client_ip not found".into()); } let mac = client_object["mac"].as_str(); if mac.is_none() { return Err("mac not found".into()); } let hostname = client_object["hostname"].as_str(); if hostname.is_none() { return Err("hostname not found".into()); } let last_query_timestamp = client_object["last_query_timestamp"].as_u64(); if last_query_timestamp.is_none() { return Err("last_query_timestamp not found".into()); } client_list.push(ClientData { id: id.unwrap() as u32, client_ip: client_ip.unwrap().to_string(), mac: mac.unwrap().to_string(), hostname: hostname.unwrap().to_string(), last_query_timestamp: last_query_timestamp.unwrap(), }); } Ok(client_list) } pub fn api_msg_gen_json_object_client(client: &ClientData) -> serde_json::Value { json!({ "id": client.id, "client_ip": client.client_ip, "mac": client.mac, "hostname": client.hostname, "last_query_timestamp": client.last_query_timestamp, }) } pub fn api_msg_gen_client_list( client_list_result: &QueryClientListResult, total_page: u64, total_count: u64, ) -> String { let json_str = json!({ "list_count": client_list_result.client_list.len(), "total_page": total_page, "total_count": total_count, "step_by_cursor": client_list_result.step_by_cursor, "client_list": client_list_result.client_list .iter() .map(|x| { api_msg_gen_json_object_client(x) }) .collect::>() }); json_str.to_string() } pub fn api_msg_auth_token(token: &str, expired: &str) -> String { let json_str = json!({ "token": token, "token_type": "Bearer", "expires_in": expired, }); json_str.to_string() } pub fn api_msg_parse_auth_token(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let token = v["token"].as_str(); if token.is_none() { return Err("token not found".into()); } let expired = v["expires_in"].as_str(); if expired.is_none() { return Err("expires_in not found".into()); } Ok(TokenResponse { token: token.unwrap().to_string(), expires_in: expired.unwrap().to_string(), }) } pub fn api_msg_gen_cache_number(cache_number: i32) -> String { let json_str = json!({ "cache_number": cache_number, }); json_str.to_string() } pub fn api_msg_parse_cache_number(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let cache_number = v["cache_number"].as_i64(); if cache_number.is_none() { return Err("cache_number not found".into()); } Ok(cache_number.unwrap() as i32) } pub fn api_msg_error(msg: &str) -> String { let json_str = json!({ "error": msg, }); json_str.to_string() } pub fn api_msg_parse_error(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let error = v["error"].as_str(); if error.is_none() { return Err("error not found".into()); } Ok(error.unwrap().to_string()) } pub fn api_msg_parse_loglevel(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let loglevel = v["log_level"].as_str(); if loglevel.is_none() { return Err("loglevel not found".into()); } let ret = loglevel.unwrap().try_into(); if ret.is_err() { return Err("log level is invalid".into()); } Ok(ret.unwrap()) } pub fn api_msg_gen_loglevel(loglevel: LogLevel) -> String { let loglevel_str = loglevel.to_string(); let json_str = json!({ "log_level": loglevel_str, }); json_str.to_string() } pub fn api_msg_gen_version(smartdns_version: &str, ui_version: &str) -> String { let json_str = json!({ "smartdns": smartdns_version, "smartdns_ui": ui_version, }); json_str.to_string() } pub fn api_msg_gen_upstream_server_list(upstream_server_list: &Vec) -> String { let json_str = json!({ "upstream_server_list": upstream_server_list .iter() .map(|x| { let s = json!({ "host": x.host, "ip": x.ip, "port": x.port, "server_type": x.server_type.to_string(), "total_query_count": x.total_query_count, "total_query_success": x.total_query_success, "total_query_recv_count": x.total_query_recv_count, "query_success_rate": x.query_success_rate, "avg_time": x.avg_time, "status": x.status, "security": x.security, }); s }) .collect::>() }); json_str.to_string() } pub fn api_msg_parse_upstream_server_list( data: &str, ) -> Result, Box> { let v: serde_json::Value = serde_json::from_str(data)?; let mut upstream_server_list = Vec::new(); let server_list = v["upstream_server_list"].as_array(); if server_list.is_none() { return Err("list_count not found".into()); } for item in server_list.unwrap() { let host = item["host"].as_str(); if host.is_none() { return Err("host not found".into()); } let ip = item["ip"].as_str(); if ip.is_none() { return Err("ip not found".into()); } let port = item["port"].as_u64(); if port.is_none() { return Err("port not found".into()); } let server_type = item["server_type"].as_str(); if server_type.is_none() { return Err("server_type not found".into()); } let total_query_count = item["total_query_count"].as_u64(); if total_query_count.is_none() { return Err("total_query_count not found".into()); } let total_query_success = item["total_query_success"].as_u64(); if total_query_success.is_none() { return Err("total_query_success not found".into()); } let total_query_recv_count = item["total_query_recv_count"].as_u64(); if total_query_recv_count.is_none() { return Err("total_query_recv_count not found".into()); } let query_success_rate = item["query_success_rate"].as_f64(); if query_success_rate.is_none() { return Err("query_success_rate not found".into()); } let avg_time = item["avg_time"].as_f64(); if avg_time.is_none() { return Err("avg_time not found".into()); } let status = item["status"].as_str(); if status.is_none() { return Err("status not found".into()); } let security = item["security"].as_str(); if security.is_none() { return Err("security not found".into()); } upstream_server_list.push(UpstreamServerInfo { host: host.unwrap().to_string(), ip: ip.unwrap().to_string(), port: port.unwrap() as u16, server_type: server_type.unwrap().parse()?, total_query_count: total_query_count.unwrap() as u64, total_query_success: total_query_success.unwrap() as u64, total_query_recv_count: total_query_recv_count.unwrap() as u64, query_success_rate: query_success_rate.unwrap(), avg_time: avg_time.unwrap(), status: status.unwrap().to_string(), security: security.unwrap().to_string(), }); } Ok(upstream_server_list) } pub fn api_msg_parse_version(data: &str) -> Result<(String, String), Box> { let v: serde_json::Value = serde_json::from_str(data)?; let smartdns = v["smartdns"].as_str(); if smartdns.is_none() { return Err("smartdns not found".into()); } let ui = v["smartdns_ui"].as_str(); if ui.is_none() { return Err("ui not found".into()); } Ok((smartdns.unwrap().to_string(), ui.unwrap().to_string())) } pub fn api_msg_gen_key_value(data: &HashMap) -> String { let mut json_map = serde_json::Map::new(); for (key, value) in data { json_map.insert(key.clone(), serde_json::Value::String(value.clone())); } serde_json::Value::Object(json_map).to_string() } pub fn api_msg_parse_key_value(data: &str) -> Result, Box> { let v: serde_json::Value = serde_json::from_str(data)?; let mut conf_map = HashMap::new(); if let serde_json::Value::Object(map) = v { for (key, value) in map { if let serde_json::Value::String(value_str) = value { conf_map.insert(key, value_str); } } } Ok(conf_map) } pub fn api_msg_gen_top_client_list(client_list: &Vec) -> String { let json_str = json!({ "client_top_list": client_list .iter() .map(|x| { let s = json!({ "client_ip": x.client_ip, "query_count": x.count, "timestamp_start": x.timestamp_start, "timestamp_end": x.timestamp_end, }); s }) .collect::>() }); json_str.to_string() } pub fn api_msg_parse_top_client_list(data: &str) -> Result, Box> { let v: serde_json::Value = serde_json::from_str(data)?; let mut client_list = Vec::new(); let top_list = v["client_top_list"].as_array(); if top_list.is_none() { return Err("list_count not found".into()); } for item in top_list.unwrap() { let client_ip = item["client_ip"].as_str(); if client_ip.is_none() { return Err("client_ip not found".into()); } let query_count = item["query_count"].as_u64(); if query_count.is_none() { return Err("query_count not found".into()); } let timestamp_start = item["timestamp_start"].as_u64(); if timestamp_start.is_none() { return Err("timestamp_start not found".into()); } let timestamp_end = item["timestamp_end"].as_u64(); if timestamp_end.is_none() { return Err("timestamp_end not found".into()); } client_list.push(ClientQueryCount { client_ip: client_ip.unwrap().to_string(), count: query_count.unwrap() as u32, timestamp_start: timestamp_start.unwrap(), timestamp_end: timestamp_end.unwrap(), }); } Ok(client_list) } pub fn api_msg_gen_top_domain_list(domain_list: &Vec) -> String { let json_str = json!({ "domain_top_list": domain_list .iter() .map(|x| { let s = json!({ "domain": x.domain, "query_count": x.count, "timestamp_start": x.timestamp_start, "timestamp_end": x.timestamp_end, }); s }) .collect::>() }); json_str.to_string() } pub fn api_msg_parse_top_domain_list(data: &str) -> Result, Box> { let v: serde_json::Value = serde_json::from_str(data)?; let mut domain_list = Vec::new(); let top_list = v["domain_top_list"].as_array(); if top_list.is_none() { return Err("list_count not found".into()); } for item in top_list.unwrap() { let domain = item["domain"].as_str(); if domain.is_none() { return Err("domain not found".into()); } let query_count = item["query_count"].as_u64(); if query_count.is_none() { return Err("query_count not found".into()); } let timestamp_start = item["timestamp_start"].as_u64(); if timestamp_start.is_none() { return Err("timestamp_start not found".into()); } let timestamp_end = item["timestamp_end"].as_u64(); if timestamp_end.is_none() { return Err("timestamp_end not found".into()); } domain_list.push(DomainQueryCount { domain: domain.unwrap().to_string(), count: query_count.unwrap() as u32, timestamp_start: timestamp_start.unwrap(), timestamp_end: timestamp_end.unwrap(), }); } Ok(domain_list) } pub fn api_msg_gen_metrics_data(data: &MetricsData) -> String { let json_str = json!({ "total_query_count": data.total_query_count, "block_query_count": data.block_query_count, "request_drop_count": data.request_drop_count, "fail_query_count": data.fail_query_count, "avg_query_time": data.avg_query_time, "cache_hit_rate": data.cache_hit_rate, "cache_number": data.cache_number, "cache_memory_size": data.cache_memory_size, "qps": data.qps, "memory_usage": data.memory_usage, "is_metrics_suspended": data.is_metrics_suspended, }); json_str.to_string() } pub fn api_msg_parse_metrics_data(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let total_query_count = v["total_query_count"].as_u64(); if total_query_count.is_none() { return Err("total_query_count not found".into()); } let block_query_count = v["block_query_count"].as_u64(); if block_query_count.is_none() { return Err("block_query_count not found".into()); } let request_drop_count = v["request_drop_count"].as_u64(); if request_drop_count.is_none() { return Err("request_drop_count not found".into()); } let fail_query_count = v["fail_query_count"].as_u64(); if fail_query_count.is_none() { return Err("fail_query_count not found".into()); } let avg_query_time = v["avg_query_time"].as_f64(); if avg_query_time.is_none() { return Err("avg_query_time not found".into()); } let cache_hit_rate = v["cache_hit_rate"].as_f64(); if cache_hit_rate.is_none() { return Err("cache_hit_rate not found".into()); } let cache_number = v["cache_number"].as_u64(); if cache_number.is_none() { return Err("cache_number not found".into()); } let cache_memory_size = v["cache_memory_size"].as_u64(); if cache_memory_size.is_none() { return Err("cache_memory_size not found".into()); } let qps = v["qps"].as_u64(); if qps.is_none() { return Err("qps not found".into()); } let memory_usage = v["memory_usage"].as_u64(); if memory_usage.is_none() { return Err("memory_usage not found".into()); } let is_metrics_suspended = v["is_metrics_suspended"].as_bool(); Ok(MetricsData { total_query_count: total_query_count.unwrap() as u64, block_query_count: block_query_count.unwrap() as u64, request_drop_count: request_drop_count.unwrap() as u64, fail_query_count: fail_query_count.unwrap() as u64, avg_query_time: avg_query_time.unwrap(), cache_hit_rate: cache_hit_rate.unwrap(), cache_number: cache_number.unwrap() as u64, cache_memory_size: cache_memory_size.unwrap() as u64, qps: qps.unwrap() as u32, memory_usage: memory_usage.unwrap() as u64, is_metrics_suspended: is_metrics_suspended.unwrap_or(false), }) } pub fn api_msg_gen_stats_overview(data: &OverviewData) -> String { let json_str = json!({ "server_name": data.server_name, "database_size": data.db_size, "startup_timestamp": data.startup_timestamp, "free_disk_space": data.free_disk_space, "is_process_suspended": data.is_process_suspended, }); json_str.to_string() } pub fn api_msg_parse_stats_overview(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let server_name = v["server_name"].as_str(); if server_name.is_none() { return Err("server_name not found".into()); } let db_size = v["database_size"].as_u64(); if db_size.is_none() { return Err("database_size not found".into()); } let startup_timestamp = v["startup_timestamp"].as_u64(); if startup_timestamp.is_none() { return Err("startup_timestamp not found".into()); } let free_disk_space = v["free_disk_space"].as_u64(); if free_disk_space.is_none() { return Err("free_disk_space not found".into()); } let is_process_suspended = v["is_process_suspended"].as_bool(); if is_process_suspended.is_none() { return Err("is_process_suspended not found".into()); } Ok(OverviewData { server_name: server_name.unwrap().to_string(), db_size: db_size.unwrap() as u64, startup_timestamp: startup_timestamp.unwrap() as u64, free_disk_space: free_disk_space.unwrap() as u64, is_process_suspended: is_process_suspended.unwrap(), }) } pub fn api_msg_gen_hourly_query_count(hourly_count: &HourlyQueryCount) -> String { let json_str = json!({ "query_timestamp": hourly_count.query_timestamp, "hourly_query_count": hourly_count.hourly_query_count .iter() .map(|x| { let s = json!({ "hour": x.hour, "query_count": x.query_count, }); s }) .collect::>() }); json_str.to_string() } pub fn api_msg_parse_hourly_query_count(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let query_timestamp = v["query_timestamp"].as_u64(); if query_timestamp.is_none() { return Err("query_timestamp not found".into()); } let mut hourly_query_count = Vec::new(); let hourly_list = v["hourly_query_count"].as_array(); if hourly_list.is_none() { return Err("hourly_query_count not found".into()); } for item in hourly_list.unwrap() { let hour = item["hour"].as_str(); if hour.is_none() { return Err("hour not found".into()); } let query_count = item["query_count"].as_u64(); if query_count.is_none() { return Err("query_count not found".into()); } hourly_query_count.push(HourlyQueryCountItem { hour: hour.unwrap().to_string(), query_count: query_count.unwrap() as u32, }); } Ok(HourlyQueryCount { query_timestamp: query_timestamp.unwrap(), hourly_query_count: hourly_query_count, }) } pub fn api_msg_gen_request_qps(qps: u32) -> String { let json_str = json!({ "qps": qps, }); json_str.to_string() } pub fn api_msg_gen_daily_query_count(daily_count: &DailyQueryCount) -> String { let json_str = json!({ "query_timestamp": daily_count.query_timestamp, "daily_query_count": daily_count.daily_query_count .iter() .map(|x| { let s = json!({ "day": x.day, "query_count": x.query_count, }); s }) .collect::>() }); json_str.to_string() } pub fn api_msg_parse_daily_query_count(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let mut daily_query_count = Vec::new(); let query_timestamp = v["query_timestamp"].as_u64(); if query_timestamp.is_none() { return Err("query_timestamp not found".into()); } let daily_list = v["daily_query_count"].as_array(); if daily_list.is_none() { return Err("daily_query_count not found".into()); } for item in daily_list.unwrap() { let day = item["day"].as_str(); if day.is_none() { return Err("day not found".into()); } let query_count = item["query_count"].as_u64(); if query_count.is_none() { return Err("query_count not found".into()); } daily_query_count.push(DailyQueryCountItem { day: day.unwrap().to_string(), query_count: query_count.unwrap() as u32, }); } Ok(DailyQueryCount { query_timestamp: query_timestamp.unwrap(), daily_query_count: daily_query_count, }) } pub fn api_msg_gen_whois_info(data: &WhoIsInfo) -> String { let json_str = json!({ "domain": data.domain, "registrar": data.registrar, "organization": data.organization, "address": data.address, "city": data.city, "country": data.country, }); json_str.to_string() } pub fn api_msg_parse_whois_info(data: &str) -> Result> { let v: serde_json::Value = serde_json::from_str(data)?; let domain = v["domain"].as_str(); if domain.is_none() { return Err("domain not found".into()); } let registrar = v["registrar"].as_str(); if registrar.is_none() { return Err("registrar not found".into()); } let organization = v["organization"].as_str(); if organization.is_none() { return Err("organization not found".into()); } let address = v["address"].as_str(); if address.is_none() { return Err("address not found".into()); } let city = v["city"].as_str(); if city.is_none() { return Err("city not found".into()); } let country = v["country"].as_str(); if country.is_none() { return Err("country not found".into()); } let refer = v["refer"].as_str(); let refer = if refer.is_none() { String::new() } else { refer.unwrap().to_string() }; Ok(WhoIsInfo { refer: refer, domain: domain.unwrap().to_string(), registrar: registrar.unwrap().to_string(), organization: organization.unwrap().to_string(), address: address.unwrap().to_string(), city: city.unwrap().to_string(), country: country.unwrap().to_string(), }) } ================================================ FILE: plugin/smartdns-ui/src/http_error.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 std::string::FromUtf8Error; use crate::http_api_msg::*; use bytes::Bytes; use http_body_util::Full; use hyper::{Response, StatusCode}; #[derive(Debug)] pub struct HttpError { pub code: StatusCode, pub msg: String, } impl HttpError { pub fn new(code: StatusCode, msg: String) -> Self { HttpError { code: code, msg: msg.to_string(), } } pub fn to_response(&self) -> Response> { let bytes = Bytes::from(api_msg_error(&self.msg)); let mut response = Response::new(Full::new(bytes)); response .headers_mut() .insert("Content-Type", "application/json".parse().unwrap()); *response.status_mut() = self.code; response } } impl From for HttpError { fn from(err: hyper::Error) -> HttpError { HttpError { code: StatusCode::INTERNAL_SERVER_ERROR, msg: format!("Hyper error: {}", err), } } } impl From for HttpError { fn from(err: FromUtf8Error) -> HttpError { HttpError { code: StatusCode::BAD_REQUEST, msg: format!("FromUtf8Error: {}", err), } } } impl From for HttpError { fn from(err: std::io::Error) -> HttpError { HttpError { code: StatusCode::INTERNAL_SERVER_ERROR, msg: format!("IO error: {}", err), } } } impl From> for HttpError { fn from(err: Box) -> HttpError { HttpError { code: StatusCode::INTERNAL_SERVER_ERROR, msg: format!("Error: {}", err), } } } impl From> for HttpError { fn from(err: Box) -> HttpError { HttpError { code: StatusCode::INTERNAL_SERVER_ERROR, msg: format!("Error: {}", err), } } } ================================================ FILE: plugin/smartdns-ui/src/http_jwt.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 jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct JwtClaims { pub user: String, pub ip: String, pub exp: u64, } pub struct Jwt { user: String, secret: String, ip: String, expired_in: u32, } pub struct TokenInfo { pub token: String, pub expire: String, } impl Jwt { pub fn new(user: &str, secret: &str, ip: &str, expired_in: u32) -> Self { Jwt { user: user.to_string(), secret: secret.to_string(), ip: ip.to_string(), expired_in: expired_in, } } pub fn refresh_token(&self, token: &str) -> Result { if !self.is_token_valid(token) { return Err("token is invalid".to_string()); } Ok(self.encode_token()) } pub fn encode_token(&self) -> TokenInfo { let calims = JwtClaims { user: self.user.clone(), ip: self.ip.clone(), exp: jsonwebtoken::get_current_timestamp() + self.expired_in as u64, }; let token = encode( &Header::default(), &calims, &EncodingKey::from_secret(self.secret.as_ref()), ); let exp = self.expired_in.to_string(); TokenInfo { token: token.unwrap(), expire: exp, } } pub fn is_token_valid(&self, token: &str) -> bool { let calim = self.decode_token(token); if self.decode_token(token).is_err() { return false; } let calim = calim.unwrap(); if calim.user != self.user || calim.ip != self.ip { return false; } true } pub fn decode_token(&self, token: &str) -> Result { let calims = decode::( &token, &DecodingKey::from_secret(self.secret.as_ref()), &Validation::default(), ); match calims { Ok(c) => Ok(c.claims), Err(e) => Err(e.to_string()), } } } ================================================ FILE: plugin/smartdns-ui/src/http_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 . */ extern crate cfg_if; use crate::data_server::*; use crate::dns_log; use crate::http_api_msg::*; use crate::http_jwt::*; use crate::http_server_api::*; use crate::plugin::SmartdnsPlugin; use crate::smartdns::*; use crate::utils; use bytes::Bytes; use http_body_util::Full; use hyper::body; use hyper::header::HeaderValue; use hyper::StatusCode; use hyper::{service::service_fn, Request, Response}; use hyper_util::rt::TokioIo; use hyper_util::server::conn::auto; use std::convert::Infallible; use std::error::Error; use std::fs::Metadata; use std::net::SocketAddr; use std::path::PathBuf; use std::path::{Component, Path}; use std::sync::MutexGuard; use std::sync::Weak; use std::sync::{Arc, Mutex}; use std::time::Duration; use std::time::Instant; use tokio::fs::read; use tokio::net::TcpListener; use tokio::net::TcpStream; use tokio::sync::mpsc; use tokio::task::JoinHandle; cfg_if::cfg_if! { if #[cfg(feature = "https")] { use rustls_pemfile; use std::io::BufReader; use tokio_rustls::{rustls, TlsAcceptor}; } } const HTTP_SERVER_DEFAULT_PASSWORD: &str = "password"; const HTTP_SERVER_DEFAULT_USERNAME: &str = "admin"; const HTTP_SERVER_DEFAULT_WWW_ROOT: &str = "/usr/share/smartdns/wwwroot"; const HTTP_SERVER_DEFAULT_IPV6: &str = "http://[::]:6080"; const HTTP_SERVER_DEFAULT_IP: &str = "http://0.0.0.0:6080"; #[derive(Clone)] pub struct HttpServerConfig { pub http_ip: String, pub http_root: String, pub username: String, pub password: String, pub token_expired_time: u32, pub enable_cors: bool, pub enable_terminal: bool, } impl HttpServerConfig { pub fn new() -> Self { let host_ip = if utils::is_ipv6_supported() { HTTP_SERVER_DEFAULT_IPV6.to_string() } else { HTTP_SERVER_DEFAULT_IP.to_string() }; HttpServerConfig { http_ip: host_ip, http_root: HTTP_SERVER_DEFAULT_WWW_ROOT.to_string(), username: HTTP_SERVER_DEFAULT_USERNAME.to_string(), password: utils::hash_password(HTTP_SERVER_DEFAULT_PASSWORD, Some(1000)).unwrap(), token_expired_time: 600, enable_cors: false, enable_terminal: false, } } pub fn settings_map(&self) -> std::collections::HashMap { let mut map = std::collections::HashMap::new(); map.insert("http_ip".to_string(), self.http_ip.clone()); map.insert("username".to_string(), self.username.clone()); map.insert( "token_expired_time".to_string(), self.token_expired_time.to_string(), ); map.insert("enable_cors".to_string(), self.enable_cors.to_string()); map.insert( "enable_terminal".to_string(), self.enable_terminal.to_string(), ); map } pub fn load_config(&mut self, data_server: Arc) -> Result<(), Box> { if let Some(password) = data_server.get_config("smartdns-ui.password") { self.password = password; } else { if let Some(password_from_file) = data_server.get_server_config_from_file("smartdns-ui.password") { self.password = utils::hash_password(password_from_file.as_str(), Some(10000)).unwrap(); } } if let Some(username) = data_server.get_server_config("smartdns-ui.user") { self.username = username; } if let Some(enable_cors) = data_server.get_server_config("smartdns-ui.enable-cors") { if enable_cors.eq_ignore_ascii_case("yes") || enable_cors.eq_ignore_ascii_case("true") { self.enable_cors = true; } else { self.enable_cors = false; } } if let Some(enable_terminal) = data_server.get_server_config("smartdns-ui.enable-terminal") { if enable_terminal.eq_ignore_ascii_case("yes") || enable_terminal.eq_ignore_ascii_case("true") { self.enable_terminal = true; } else { self.enable_terminal = false; } } Ok(()) } } pub struct HttpServerControl { http_server: Arc, server_thread: Mutex>>, plugin: Mutex>, } #[allow(dead_code)] impl HttpServerControl { pub fn new() -> Self { HttpServerControl { http_server: Arc::new(HttpServer::new()), server_thread: Mutex::new(None), plugin: Mutex::new(Weak::new()), } } pub fn set_plugin(&self, plugin: Arc) { *self.plugin.lock().unwrap() = Arc::downgrade(&plugin); } pub fn get_plugin(&self) -> Result, Box> { let plugin = match self.plugin.lock() { Ok(plugin) => plugin, Err(_) => return Err("Failed to lock plugin mutex".into()), }; if let Some(plugin) = plugin.upgrade() { return Ok(plugin); } Err("Plugin is not set".into()) } pub fn get_http_server(&self) -> Arc { Arc::clone(&self.http_server) } pub fn start_http_server(&self, conf: &HttpServerConfig) -> Result<(), Box> { let inner_clone = Arc::clone(&self.http_server); let ret = inner_clone.set_conf(conf); if let Err(e) = ret { return Err(e); } let plugin = self.get_plugin()?; inner_clone.set_plugin(plugin.clone()); let (tx, rx) = tokio::sync::oneshot::channel::(); let rt = plugin.get_runtime(); let server_thread = rt.spawn(async move { let ret = HttpServer::http_server_loop(inner_clone, tx).await; if let Err(e) = ret { dns_log!(LogLevel::ERROR, "http server error: {}", e); Plugin::smartdns_exit(1); } dns_log!(LogLevel::DEBUG, "http server exit."); }); tokio::task::block_in_place(|| { let _ = rt.block_on(rx); }); *self.server_thread.lock().unwrap() = Some(server_thread); Ok(()) } pub fn stop_http_server(&self) { let mut server_thread = self.server_thread.lock().unwrap(); if server_thread.is_none() { return; } self.http_server.stop_http_server(); if let Some(server_thread) = server_thread.take() { let plugin = self.get_plugin(); if plugin.is_err() { dns_log!( LogLevel::ERROR, "get plugin error: {}", plugin.err().unwrap() ); return; } let plugin = plugin.unwrap(); let rt = plugin.get_runtime(); tokio::task::block_in_place(|| { if let Err(e) = rt.block_on(server_thread) { dns_log!(LogLevel::ERROR, "http server stop error: {}", e); } }); } } } impl Drop for HttpServerControl { fn drop(&mut self) { self.stop_http_server(); } } #[derive(Clone)] pub struct TokioExecutor; impl hyper::rt::Executor for TokioExecutor where F: std::future::Future + Send + 'static, F::Output: Send + 'static, { fn execute(&self, fut: F) { tokio::task::spawn(fut); } } pub struct HttpServer { conf: Mutex, notify_tx: Option>, notify_rx: Mutex>>, api: API, local_addr: Mutex>, mime_map: std::collections::HashMap<&'static str, &'static str>, login_attempts: Mutex<(i32, Instant)>, plugin: Mutex>, } #[allow(dead_code)] impl HttpServer { fn new() -> Self { let mut plugin = HttpServer { conf: Mutex::new(HttpServerConfig::new()), notify_tx: None, notify_rx: Mutex::new(None), api: API::new(), local_addr: Mutex::new(None), login_attempts: Mutex::new((0, Instant::now())), plugin: Mutex::new(Weak::new()), mime_map: std::collections::HashMap::from([ /* text */ ("htm", "text/html"), ("html", "text/html"), ("js", "text/javascript"), ("css", "text/css"), ("txt", "text/plain"), ("conf", "text/plain"), ("xml", "text/xml"), ("csv", "text/csv"), ("md", "text/markdown"), /* image */ ("png", "image/png"), ("gif", "image/gif"), ("jpeg", "image/jpeg"), ("svg", "image/svg+xml"), ("ico", "image/x-icon"), ("bmp", "image/bmp"), ("avif", "image/avif"), /* video */ ("mpeg", "video/mpeg"), ("mp4", "video/mp4"), ("webm", "video/webm"), /* audio */ ("mp3", "audio/mpeg"), ("ogg", "audio/ogg"), ("wav", "audio/wav"), /* font */ ("woff", "font/woff"), ("woff2", "font/woff2"), ("ttf", "font/ttf"), ("otf", "font/otf"), /* application */ ("wasm", "application/wasm"), ("pdf", "application/pdf"), ("json", "application/json"), ("tar", "application/x-tar"), ("zip", "application/zip"), ]), }; let (tx, rx) = mpsc::channel(100); plugin.notify_tx = Some(tx); plugin.notify_rx = Mutex::new(Some(rx)); plugin } pub fn get_conf(&self) -> HttpServerConfig { let conf = self.conf.lock().unwrap(); conf.clone() } pub fn get_conf_mut(&'_ self) -> MutexGuard<'_, HttpServerConfig> { self.conf.lock().unwrap() } pub fn login_attempts_reset(&self) { let mut attempts = self.login_attempts.lock().unwrap(); attempts.0 = 0; attempts.1 = Instant::now(); } pub fn login_attempts_check(&self) -> bool { let mut attempts = self.login_attempts.lock().unwrap(); if attempts.0 == 0 { attempts.1 = Instant::now(); } attempts.0 += 1; if attempts.0 > 5 { let now = Instant::now(); let duration = now.duration_since(attempts.1); if duration.as_secs() < 60 { if duration.as_secs() < 30 { attempts.1 = Instant::now(); } return false; } attempts.0 = 0; attempts.1 = now; } true } pub fn get_local_addr(&self) -> Option { let local_addr = self.local_addr.lock().unwrap(); local_addr.clone() } fn set_conf(&self, conf: &HttpServerConfig) -> Result<(), Box> { let mut conf_clone = self.conf.lock().unwrap(); *conf_clone = conf.clone(); dns_log!(LogLevel::INFO, "http server URI: {}", conf_clone.http_ip); dns_log!( LogLevel::INFO, "http server www root: {}", conf_clone.http_root ); Ok(()) } fn set_plugin(&self, plugin: Arc) { let mut _plugin = self.plugin.lock().unwrap(); *_plugin = Arc::downgrade(&plugin); } fn get_plugin(&self) -> Result, Box> { let plugin = match self.plugin.lock() { Ok(plugin) => plugin, Err(_) => return Err("Failed to lock plugin mutex".into()), }; if let Some(plugin) = plugin.upgrade() { return Ok(plugin); } Err("Plugin is not set".into()) } pub fn is_https_server(&self) -> bool { let http_ip = self.get_conf().http_ip; if http_ip.parse::().is_err() { return false; } let binding = http_ip.parse::().unwrap(); let scheme = binding.scheme(); if scheme == "https" { return true; } false } pub fn get_data_server(&self) -> Arc { self.get_plugin().unwrap().get_data_server() } pub fn get_token_from_header( req: &Request, ) -> Result, Box> { let token: String; let header_auth = req.headers().get("Authorization"); if header_auth.is_none() { let cookie = req.headers().get("Cookie"); if cookie.is_none() { return Ok(None); } let cookie = cookie.unwrap().to_str(); if let Err(_) = cookie { return Ok(None); } let cookies = cookie.unwrap().split(';').collect::>(); let token_cookie = cookies.iter().find(|c| c.trim().starts_with("token=")); if token_cookie.is_none() { return Ok(None); } let token_cookie = token_cookie.unwrap().trim().strip_prefix("token="); if token_cookie.is_none() { return Ok(None); } let data = urlencoding::decode(token_cookie.unwrap()); if let Err(_) = data { return Ok(None); } let data = data.unwrap(); token = data.to_string(); } else { let auth = header_auth.unwrap().to_str(); if let Err(_) = auth { return Ok(None); } token = auth.unwrap().to_string(); } let token_type = "Bearer"; if !token.starts_with(token_type) { return Err("Invalid authorization type".into()); } let token = token.strip_prefix(token_type).unwrap().trim(); Ok(Some(token.to_string())) } pub fn auth_token_is_valid( &self, req: &Request, ) -> Result> { let token = HttpServer::get_token_from_header(req)?; if token.is_none() { return Ok(false); } let token = token.unwrap(); let conf = self.conf.lock().unwrap(); let jwt = Jwt::new(&conf.username, &conf.password, "", conf.token_expired_time); if !jwt.is_token_valid(token.as_str()) { return Ok(false); } Ok(true) } fn server_add_cors_header( &self, origin: &Option, response: &mut Response>, ) { if self.get_conf().enable_cors { if let Some(origin) = origin { response .headers_mut() .insert("Access-Control-Allow-Origin", origin.clone()); } else { response .headers_mut() .insert("Access-Control-Allow-Origin", "*".parse().unwrap()); } response.headers_mut().insert( "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH".parse().unwrap(), ); response.headers_mut().insert( "Access-Control-Allow-Headers", "Content-Type, Authorization, Set-Cookie".parse().unwrap(), ); response .headers_mut() .insert("Access-Control-Allow-Credentials", "true".parse().unwrap()); response .headers_mut() .insert("Access-Control-Max-Age", "600".parse().unwrap()); } } async fn server_handle_http_api_request( this: Arc, req: Request, _path: PathBuf, ) -> Result>, Box> { let mut origin: Option = None; if let Some(o) = req.headers().get("Origin") { origin = Some(o.clone()); } let error_response = |code: StatusCode, msg: &str| { let bytes = Bytes::from(api_msg_error(msg)); let mut response = Response::new(Full::new(bytes)); response .headers_mut() .insert("Content-Type", "application/json".parse().unwrap()); response .headers_mut() .insert("Cache-Control", "no-cache".parse().unwrap()); *response.status_mut() = code; this.server_add_cors_header(&origin, &mut response); Ok(response) }; dns_log!(LogLevel::DEBUG, "api request: {:?}", req.uri()); if req.method() == hyper::Method::OPTIONS { let mut response = Response::new(Full::new(Bytes::from(""))); response .headers_mut() .insert("Content-Type", "application/json".parse().unwrap()); response .headers_mut() .insert("Cache-Control", "no-cache".parse().unwrap()); this.server_add_cors_header(&origin, &mut response); return Ok(response); } match this.api.get_router(req.method(), req.uri().path()) { Some((router, param)) => { if router.auth { let is_token_valid = this.auth_token_is_valid(&req); if let Err(e) = is_token_valid { return error_response(StatusCode::BAD_REQUEST, e.to_string().as_str()); } if !is_token_valid.unwrap() { return error_response(StatusCode::UNAUTHORIZED, "Please login."); } } if router.method != req.method() { return error_response(StatusCode::METHOD_NOT_ALLOWED, "Method Not Allowed"); } let resp = (router.handler)(this.clone(), param, req).await; match resp { Ok(resp) => { let mut resp = resp; if resp.headers().get("Content-Type").is_none() { resp.headers_mut() .insert("Content-Type", "application/json".parse().unwrap()); } if resp.headers().get("Cache-Control").is_none() { resp.headers_mut() .insert("Cache-Control", "no-cache".parse().unwrap()); } this.server_add_cors_header(&origin, &mut resp); Ok(resp) } Err(e) => Ok(e.to_response()), } } None => error_response(StatusCode::NOT_FOUND, "API not found."), } } pub fn get_mime_type(&self, file: &str) -> String { let ext = file.split('.').last().unwrap(); if let Some(mime) = self.mime_map.get(ext) { return mime.to_string(); } "application/octet-stream".to_string() } async fn server_handle_http_request( this: Arc, req: Request, ) -> Result>, Infallible> { let path = PathBuf::from(req.uri().path()); let mut is_404 = false; let www_root = { let conf = this.conf.lock().unwrap(); PathBuf::from(conf.http_root.clone()) }; let mut path = normalize_path(path.as_path()); if path.starts_with("/") { path = path.strip_prefix("/").unwrap().to_path_buf(); } if path.starts_with("api/") { let ret = HttpServer::server_handle_http_api_request(this, req, path.clone()).await; if let Err(e) = ret { dns_log!(LogLevel::ERROR, "api request error: {:?}", e); let mut response = Response::new(Full::new(Bytes::from("Internal Server Error"))); *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; return Ok(response); } let ret = ret.unwrap(); return Ok(ret); } dns_log!(LogLevel::DEBUG, "page request: {:?}", req.uri()); let mut filepath = www_root.join(path); let uri_path = req.uri().path().to_string(); let mut path = uri_path.clone(); if !filepath.exists() || filepath.is_dir() { let suffix = filepath.extension(); if suffix.is_none() && !uri_path.ends_with("/") { let check_filepath = filepath.with_extension("html"); if check_filepath.exists() { filepath = check_filepath; path = format!("{}.html", uri_path); } } if filepath.is_dir() { filepath = filepath.join("index.html"); path = format!("{}/index.html", uri_path); } if !filepath.exists() { filepath = www_root.join("404.html"); path = "/404.html".to_string(); if !filepath.exists() { filepath = www_root.join("index.html"); path = format!("/index.html"); } else { is_404 = true; } } } let mut file_meta: Option = None; let fn_get_etag = |meta: &Metadata| -> String { let modify_time = meta.modified(); if let Err(_) = modify_time { return "".to_string(); } format!( "{:x}-{:?}", meta.len(), modify_time .unwrap() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() ) }; if filepath.exists() { let meta = filepath.metadata(); if let Ok(meta) = meta { file_meta = Some(meta); } } let if_none_match = req.headers().get("If-None-Match"); if if_none_match.is_some() && file_meta.is_some() { let etag = fn_get_etag(&file_meta.as_ref().unwrap()); if etag == if_none_match.unwrap().to_str().unwrap() { let mut response = Response::new(Full::new(Bytes::from(""))); *response.status_mut() = StatusCode::NOT_MODIFIED; return Ok(response); } } match read(filepath).await { Ok(contents) => { let bytes = Bytes::from(contents); let bytes_len = bytes.len(); let mut response = Response::new(Full::new(bytes)); let header = response.headers_mut(); header.insert("Content-Length", bytes_len.to_string().parse().unwrap()); header.insert("Content-Type", this.get_mime_type(&path).parse().unwrap()); header.insert("Connection", "keep-alive".parse().unwrap()); header.insert("Keep-Alive", "timeout=60, max=1000".parse().unwrap()); if file_meta.as_ref().is_some() { let etag = fn_get_etag(&file_meta.as_ref().unwrap()); header.insert("ETag", etag.parse().unwrap()); } if is_404 { *response.status_mut() = StatusCode::NOT_FOUND; } else { *response.status_mut() = StatusCode::OK; } Ok(response) } Err(_) => { let bytes = Bytes::from("Page Not Found"); let mut response = Response::new(Full::new(bytes)); *response.status_mut() = StatusCode::NOT_FOUND; Ok(response) } } } async fn http_server_handle_conn(this: Arc, stream: TcpStream) { let io = TokioIo::new(stream); let handle_func = move |req| HttpServer::server_handle_http_request(this.clone(), req); tokio::task::spawn(async move { let conn = auto::Builder::new(TokioExecutor) .serve_connection_with_upgrades(io, service_fn(handle_func)) .await; if let Err(err) = conn { dns_log!(LogLevel::DEBUG, "Error serving connection: {:?}", err); return; } }); } #[cfg(feature = "https")] async fn https_server_handle_conn( this: Arc, stream: tokio_rustls::server::TlsStream, ) { let io = TokioIo::new(stream); let handle_func = move |req| HttpServer::server_handle_http_request(this.clone(), req); tokio::task::spawn(async move { let conn = auto::Builder::new(TokioExecutor) .serve_connection_with_upgrades(io, service_fn(handle_func)) .await; if let Err(err) = conn { dns_log!(LogLevel::DEBUG, "Error serving connection: {:?}", err); return; } }); } #[cfg(feature = "https")] async fn handle_tls_accept(this: Arc, acceptor: TlsAcceptor, stream: TcpStream) { tokio::task::spawn(async move { let acceptor_future = acceptor.accept(stream); let stream_ssl_tmout = tokio::time::timeout(tokio::time::Duration::from_secs(60), acceptor_future).await; if let Err(e) = stream_ssl_tmout { dns_log!(LogLevel::DEBUG, "tls accept timeout. {}", e); return; } let stream_ret = stream_ssl_tmout.unwrap(); if let Err(e) = stream_ret { dns_log!(LogLevel::DEBUG, "tls accept error: {}", e); return; } let stream_ssl = stream_ret.unwrap(); HttpServer::https_server_handle_conn(this, stream_ssl).await; }); } async fn http_server_loop( this: Arc, kickoff_tx: tokio::sync::oneshot::Sender, ) -> Result<(), Box> { let addr: String; let mut rx: mpsc::Receiver<()>; { let conf = this.conf.lock().unwrap(); addr = format!("{}", conf.http_ip); let mut _rx = this.notify_rx.lock().unwrap(); rx = _rx.take().unwrap(); } let url = addr.parse::()?; cfg_if::cfg_if! { if #[cfg(feature = "https")] { let mut acceptor = None; if url.scheme() == "https" { #[cfg(feature = "https")] let cert_info = Plugin::smartdns_get_cert()?; dns_log!( LogLevel::DEBUG, "cert: {}, key: {}", cert_info.cert, cert_info.key ); let cert_chain: Result>, _> = rustls_pemfile::certs(&mut BufReader::new(std::fs::File::open( cert_info.cert, )?)) .collect(); let cert_chain = cert_chain.unwrap_or_else(|_| Vec::new()); let key_der = rustls_pemfile::private_key(&mut BufReader::new( std::fs::File::open(cert_info.key)?, ))? .unwrap(); let mut config = rustls::ServerConfig::builder() .with_no_client_auth() .with_single_cert(cert_chain, key_der)?; config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; acceptor = Some(TlsAcceptor::from(Arc::new(config))); } } else { if url.scheme() == "https" { return Err("https is not supported.".into()); } } } let host = url.host_str().unwrap_or("127.0.0.1"); let port = url.port().unwrap_or(80); let sock_addr = format!("{}:{}", host, port).parse::()?; let listner = TcpListener::bind(sock_addr).await?; let addr = listner.local_addr()?; *this.local_addr.lock().unwrap() = Some(addr); dns_log!(LogLevel::INFO, "http server listen at {}", addr); let _ = kickoff_tx.send(0); loop { tokio::select! { _ = rx.recv() => { break; } res = listner.accept() => { match res { Ok((stream, _)) => { let sock_ref = socket2::SockRef::from(&stream); let mut ka = socket2::TcpKeepalive::new(); ka = ka.with_time(Duration::from_secs(60)); ka = ka.with_interval(Duration::from_secs(30)); sock_ref.set_tcp_keepalive(&ka)?; sock_ref.set_nonblocking(true)?; sock_ref.tcp_nodelay()?; if let Err(_) = sock_ref.set_recv_buffer_size(262144) { dns_log!(LogLevel::DEBUG, "Failed to set recv buffer size"); } if let Err(_) = sock_ref.set_send_buffer_size(262144) { dns_log!(LogLevel::DEBUG, "Failed to set send buffer size"); } cfg_if::cfg_if! { if #[cfg(feature = "https")] { if acceptor.is_some() { let acceptor = acceptor.clone().unwrap().clone(); let this_clone = this.clone(); HttpServer::handle_tls_accept(this_clone, acceptor, stream).await; } else { HttpServer::http_server_handle_conn(this.clone(), stream).await; } } else { HttpServer::http_server_handle_conn(this.clone(), stream).await; } } } Err(e) => { dns_log!(LogLevel::ERROR, "accept error: {}", e); } } } } } Ok(()) } fn stop_http_server(&self) { if let Some(tx) = self.notify_tx.as_ref().cloned() { let plugin = match self.get_plugin() { Ok(plugin) => plugin, Err(e) => { dns_log!(LogLevel::ERROR, "get plugin error: {}", e); return; } }; let rt = plugin.get_runtime(); tokio::task::block_in_place(|| { let _ = rt.block_on(async { let _ = tx.send(()).await; }); }); } } } pub fn normalize_path(path: &Path) -> PathBuf { let mut components = path.components().peekable(); let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { components.next(); PathBuf::from(c.as_os_str()) } else { PathBuf::new() }; for component in components { match component { Component::Prefix(..) => unreachable!(), Component::RootDir => { ret.push(component.as_os_str()); } Component::CurDir => {} Component::ParentDir => { ret.pop(); } Component::Normal(c) => { ret.push(c); } } } ret } ================================================ FILE: plugin/smartdns-ui/src/http_server_api.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::db::*; use crate::dns_log; use crate::http_api_msg::*; use crate::http_error::*; use crate::http_jwt::*; use crate::http_server::*; use crate::http_server_stream; use crate::smartdns; use crate::smartdns::*; use crate::utils; use crate::Plugin; use bytes::Bytes; use http_body_util::BodyExt; use http_body_util::Full; use hyper::{body, Method, Request, Response, StatusCode}; use matchit::Router; use std::collections::HashMap; use std::future::Future; use std::pin::Pin; use std::sync::Arc; use url::form_urlencoded; const PASSWORD_CONFIG_KEY: &str = "smartdns-ui.password"; const REST_API_PATH: &str = "/api"; type APIRouteFuture<'a, T> = Pin + Send + 'a>>; type APIRouterFun = fn( this: Arc, param: APIRouteParam, req: Request, ) -> APIRouteFuture<'static, Result>, HttpError>>; type APIRouteParam = HashMap; pub struct APIRouter { pub method: Method, pub auth: bool, pub handler: APIRouterFun, } pub struct API { router: Router>, } macro_rules! APIRoute { ( $fn:path) => { |r, p, h| Box::pin($fn(r, p, h)) }; } #[allow(dead_code)] impl API { #[rustfmt::skip] pub fn new() -> Self { let mut api = API { router: Router::new(), }; api.register(Method::PUT, "/api/service/restart", true, APIRoute!(API::api_service_restart)); api.register(Method::PUT, "/api/cache/flush", true, APIRoute!(API::api_cache_flush)); api.register(Method::GET, "/api/cache/count", true, APIRoute!(API::api_cache_count)); api.register(Method::POST, "/api/auth/login", false, APIRoute!(API::api_auth_login)); api.register(Method::POST, "/api/auth/logout", false, APIRoute!(API::api_auth_logout)); api.register(Method::GET, "/api/auth/check", true, APIRoute!(API::api_auth_check)); api.register(Method::PUT, "/api/auth/password", false, APIRoute!(API::api_auth_change_password)); api.register(Method::POST, "/api/auth/refresh", true, APIRoute!(API::api_auth_refresh)); api.register(Method::GET, "/api/domain", true, APIRoute!(API::api_domain_get_list)); api.register(Method::DELETE, "/api/domain", true, APIRoute!(API::api_domain_delete_list)); api.register(Method::GET, "/api/domain/count", true, APIRoute!(API::api_domain_get_list_count)); api.register(Method::GET, "/api/domain/{id}", true, APIRoute!(API::api_domain_get_by_id)); api.register(Method::DELETE, "/api/domain/{id}", true, APIRoute!(API::api_domain_delete_by_id)); api.register(Method::GET, "/api/client", true, APIRoute!(API::api_client_get_list)); api.register(Method::DELETE, "/api/client/{id}", true, APIRoute!(API::api_client_delete_by_id)); api.register(Method::GET, "/api/log/stream", true, APIRoute!(API::api_log_stream)); api.register(Method::PUT, "/api/log/level", true, APIRoute!(API::api_log_set_level)); api.register(Method::GET, "/api/log/level", true, APIRoute!(API::api_log_get_level)); api.register(Method::GET, "/api/log/audit/stream", true, APIRoute!(API::api_audit_log_stream)); api.register(Method::GET, "/api/server/version", false, APIRoute!(API::api_server_version)); api.register(Method::GET, "/api/upstream-server", true, APIRoute!(API::api_upstream_server_get_list)); api.register(Method::GET, "/api/config/settings", true, APIRoute!(API::api_config_get_settings)); api.register(Method::PUT, "/api/config/settings", true, APIRoute!(API::api_config_set_settings)); api.register(Method::GET, "/api/stats/top/client", true, APIRoute!(API::api_stats_get_top_client)); api.register(Method::GET, "/api/stats/top/domain", true, APIRoute!(API::api_stats_get_top_domain)); api.register(Method::GET, "/api/stats/metrics", true, APIRoute!(API::api_stats_get_metrics)); api.register(Method::GET, "/api/stats/overview", true, APIRoute!(API::api_stats_get_overview)); api.register(Method::GET, "/api/stats/hourly-query-count", true, APIRoute!(API::api_stats_get_hourly_query_count)); api.register(Method::GET, "/api/stats/daily-query-count", true, APIRoute!(API::api_stats_get_daily_query_count)); api.register(Method::PUT, "/api/stats/refresh", true, APIRoute!(API::api_stats_refresh)); api.register(Method::GET, "/api/whois", true, APIRoute!(API::api_whois)); api.register(Method::GET, "/api/tool/term", true, APIRoute!(API::api_tool_term)); api } pub fn register(&mut self, method: Method, path: &str, auth: bool, handler: APIRouterFun) { let route_data = APIRouter { method: method.clone(), auth: auth, handler: handler, }; let mut m = self.router.at_mut(path); if m.is_err() { let map_new = std::collections::HashMap::new(); _ = self.router.insert(path, map_new); m = self.router.at_mut(path); if m.is_err() { return; } } let m = m.unwrap(); let mutmethod_map = m.value; mutmethod_map.insert(method, route_data); } pub fn get_router(&self, method: &Method, path: &str) -> Option<(&APIRouter, APIRouteParam)> { let m = self.router.at(path); if m.is_err() { return None; } let m = m.unwrap(); let method_map = m.value; let route_data = method_map.get(method); if route_data.is_none() { return None; } let route_data = route_data.unwrap(); let mut param = APIRouteParam::new(); m.params.iter().for_each(|(k, v)| { let v = v.to_string(); param.insert(k.to_string(), v); }); Some((route_data, param)) } fn get_params(req: &Request) -> HashMap { let b = req.uri().query().unwrap_or("").to_string(); form_urlencoded::parse(b.as_ref()) .into_owned() .collect::>() } fn params_parser_value(v: Option<&String>) -> Option { if v.is_none() { return None; } let v = v.unwrap(); match T::from_str(&v) { Ok(value) => Some(value), Err(_) => None, } } fn params_get_value( params: &HashMap, key: &str, ) -> Option { let v = params.get(key); if v.is_none() { return None; } let v = v.unwrap(); API::params_parser_value(Some(v)) } fn params_get_value_default( params: &HashMap, key: &str, default: T, ) -> Result { let v = params.get(key); if v.is_none() { return Ok(default); } let v = v.unwrap(); match v.parse::() { Ok(v) => return Ok(v), Err(_) => { return Err(HttpError::new( StatusCode::BAD_REQUEST, format!("Invalid parameter: {}", key), )); } } } pub fn response_error(code: StatusCode, msg: &str) -> Result>, HttpError> { let bytes = Bytes::from(api_msg_error(msg)); let mut response = Response::new(Full::new(bytes)); response .headers_mut() .insert("Content-Type", "application/json".parse().unwrap()); *response.status_mut() = code; Ok(response) } pub fn response_build( code: StatusCode, body: String, ) -> Result>, HttpError> { let mut response = Response::new(Full::new(Bytes::from(body))); response .headers_mut() .insert("Content-Type", "application/json".parse().unwrap()); *response.status_mut() = code; Ok(response) } async fn api_auth_refresh( this: Arc, _param: APIRouteParam, req: Request, ) -> Result>, HttpError> { let is_https = this.is_https_server(); let token = HttpServer::get_token_from_header(&req)?; let unauth_response = || API::response_error(StatusCode::UNAUTHORIZED, "Incorrect username or password."); if token.is_none() { return unauth_response(); } let token = token.unwrap(); let conf = this.get_conf(); let jtw = Jwt::new( &conf.username.as_str(), conf.password.as_str(), "", conf.token_expired_time, ); let calim = jtw.decode_token(token.as_str()); if calim.is_err() { return unauth_response(); } let token_new = jtw.refresh_token(token.as_str()); if token_new.is_err() { return unauth_response(); } let token_new = token_new.unwrap(); let mut resp = API::response_build( StatusCode::OK, api_msg_auth_token(&token_new.token, &token_new.expire), ); let cookie_token = format!("Bearer {}", token_new.token); let token_urlencode = urlencoding::encode(cookie_token.as_str()); let mut cookie = format!( "token={}; HttpOnly; Max-Age={}; Path={}", token_urlencode, token_new.expire, REST_API_PATH ); if is_https && conf.enable_cors { cookie.push_str("; SameSite=None; Secure"); } resp.as_mut() .unwrap() .headers_mut() .insert(hyper::header::SET_COOKIE, cookie.parse().unwrap()); resp } /// Login /// API: POST /api/auth/login /// body: /// { /// "username": "admin" /// "password": "password" /// } async fn api_auth_login( this: Arc, _param: APIRouteParam, req: Request, ) -> Result>, HttpError> { let is_https = this.is_https_server(); let whole_body = String::from_utf8(req.into_body().collect().await?.to_bytes().into())?; let userinfo = api_msg_parse_auth(whole_body.as_str()); if let Err(e) = userinfo { return API::response_error(StatusCode::BAD_REQUEST, e.to_string().as_str()); } let conf = this.get_conf(); let userinfo = userinfo.unwrap(); if !this.login_attempts_check() { return API::response_error( StatusCode::FORBIDDEN, "Too many login attempts, please try again later.", ); } if userinfo.username != conf.username || utils::verify_password(userinfo.password.as_str(), conf.password.as_str()) != true { return API::response_error( StatusCode::UNAUTHORIZED, "Incorrect username or password.", ); } this.login_attempts_reset(); let jtw = Jwt::new( userinfo.username.as_str(), conf.password.as_str(), "", conf.token_expired_time, ); let token = jtw.encode_token(); let mut resp = API::response_build( StatusCode::OK, api_msg_auth_token(&token.token, &token.expire), ); let cookie_token = format!("Bearer {}", token.token); let token_urlencode = urlencoding::encode(cookie_token.as_str()); let mut cookie = format!( "token={}; HttpOnly; Max-Age={}; Path={}", token_urlencode, token.expire, REST_API_PATH ); if is_https && conf.enable_cors { cookie.push_str("; SameSite=None; Secure"); } resp.as_mut() .unwrap() .headers_mut() .insert(hyper::header::SET_COOKIE, cookie.parse().unwrap()); resp } async fn api_auth_logout( _this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let mut response = Response::new(Full::new(Bytes::from(""))); let cookie = format!("token=none; HttpOnly; Max-Age=1; Path={}", REST_API_PATH); response .headers_mut() .insert(hyper::header::SET_COOKIE, cookie.parse().unwrap()); *response.status_mut() = StatusCode::NO_CONTENT; Ok(response) } async fn api_auth_check( _this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { API::response_build(StatusCode::OK, "".to_string()) } async fn api_auth_change_password( this: Arc, _param: APIRouteParam, req: Request, ) -> Result>, HttpError> { let unauth_response = || API::response_error(StatusCode::UNAUTHORIZED, "Incorrect username or password."); let token = HttpServer::get_token_from_header(&req)?; let whole_body = String::from_utf8(req.into_body().collect().await?.to_bytes().into())?; if token.is_none() { return unauth_response(); } let password_info = match api_msg_parse_auth_password_change(whole_body.as_str()) { Ok(v) => v, Err(e) => { return API::response_error(StatusCode::BAD_REQUEST, e.to_string().as_str()); } }; if !this.login_attempts_check() { return API::response_error( StatusCode::FORBIDDEN, "Too many login attempts, please try again later.", ); } let mut conf = this.get_conf_mut(); if utils::verify_password(password_info.0.as_str(), conf.password.as_str()) != true { return API::response_error(StatusCode::FORBIDDEN, "Incorrect password."); } let hashed_password = match utils::hash_password(password_info.1.as_str(), Some(10000)) { Ok(v) => v, Err(e) => { return API::response_error( StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str(), ); } }; let data_server = this.get_data_server(); conf.password = hashed_password.clone(); let ret = data_server.set_config(PASSWORD_CONFIG_KEY, hashed_password.as_str()); if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } this.login_attempts_reset(); API::response_build(StatusCode::NO_CONTENT, "".to_string()) } /// Restart the service
/// API: PUT /api/service/restart /// async fn api_service_restart( _this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let mut response = Response::new(Full::new(Bytes::from(""))); response .headers_mut() .insert("Content-Type", "application/json".parse().unwrap()); *response.status_mut() = StatusCode::NO_CONTENT; Plugin::smartdns_restart(); Ok(response) } /// Get the number of cache
/// API: GET /api/cache/count /// async fn api_cache_count( _this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { API::response_build( StatusCode::OK, api_msg_gen_cache_number(Plugin::dns_cache_total_num()), ) } /// Flush the cache
/// API: PUT /api/cache/flush /// async fn api_cache_flush( _this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { Plugin::dns_cache_flush(); API::response_build( StatusCode::OK, api_msg_gen_cache_number(Plugin::dns_cache_total_num()), ) } /// Get the number of domain list
/// API: GET /api/domain/count /// async fn api_domain_get_list_count( this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let data_server = this.get_data_server(); let count = data_server.get_domain_list_count(); let body = api_msg_gen_count(count as i64); API::response_build(StatusCode::OK, body) } /// Get the domain by id
/// API: GET /api/domain/{id} async fn api_domain_get_by_id( this: Arc, param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let id = API::params_parser_value(param.get("id")); if id.is_none() { return API::response_error(StatusCode::BAD_REQUEST, "Invalid parameter."); } let id = id.unwrap(); let mut get_param = DomainListGetParam::new(); get_param.id = Some(id); let data_server = this.get_data_server(); let domain_list = data_server.get_domain_list(&get_param)?; if domain_list.domain_list.len() == 0 { return API::response_error(StatusCode::NOT_FOUND, "Not found"); } let body = api_msg_gen_domain(&domain_list.domain_list[0]); API::response_build(StatusCode::OK, body) } /// Delete the client by id
/// API: DELETE /api/client/{id} /// parameter:
async fn api_client_delete_by_id( this: Arc, param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let id = match API::params_parser_value(param.get("id")) { Some(v) => v, None => return API::response_error(StatusCode::BAD_REQUEST, "Invalid parameter."), }; let data_server = this.get_data_server(); let ret = match data_server.delete_client_by_id(id) { Ok(v) => v, Err(e) => { return API::response_error( StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str(), ) } }; if ret == 0 { return API::response_error(StatusCode::NOT_FOUND, "Not found"); } API::response_build(StatusCode::NO_CONTENT, "".to_string()) } /// Delete the domain by id
/// API: DELETE /api/domain/{id} /// async fn api_domain_delete_by_id( this: Arc, param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let id = match API::params_parser_value(param.get("id")) { Some(v) => v, None => return API::response_error(StatusCode::BAD_REQUEST, "Invalid parameter."), }; let data_server = this.get_data_server(); let ret = match data_server.delete_domain_by_id(id) { Ok(v) => v, Err(e) => { return API::response_error( StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str(), ) } }; if ret == 0 { return API::response_error(StatusCode::NOT_FOUND, "Not found"); } API::response_build(StatusCode::NO_CONTENT, "".to_string()) } /// Get the domain list
/// API: GET /api/domain
/// parameter:
/// page_num: u32: Page number
/// page_size: u32: Page size
/// domain: String: Domain
/// domain_type: String: Domain type
/// domain_group: String: Domain group
/// client: String: Client
/// reply_code: String: Reply code
/// /// async fn api_domain_get_list( this: Arc, _param: APIRouteParam, req: Request, ) -> Result>, HttpError> { let params = API::get_params(&req); let page_num = API::params_get_value_default(¶ms, "page_num", 1 as u64)?; let page_size = API::params_get_value_default(¶ms, "page_size", 10 as u64)?; if page_num == 0 || page_size == 0 { return API::response_error( StatusCode::BAD_REQUEST, "Invalid parameter: page_num or page_size", ); } let id = API::params_get_value(¶ms, "id"); let domain = API::params_get_value(¶ms, "domain"); let domain_filter_mode = API::params_get_value(¶ms, "domain_filter_mode"); let domain_type = API::params_get_value(¶ms, "domain_type"); let domain_group = API::params_get_value(¶ms, "domain_group"); let client = API::params_get_value(¶ms, "client"); let reply_code = API::params_get_value(¶ms, "reply_code"); let order = API::params_get_value(¶ms, "order"); let is_blocked = API::params_get_value(¶ms, "is_blocked"); let timestamp_after = API::params_get_value(¶ms, "timestamp_after"); let timestamp_before = API::params_get_value(¶ms, "timestamp_before"); let cursor = API::params_get_value(¶ms, "cursor"); let cursor_direction = match API::params_get_value_default(¶ms, "cursor_direction", "next".to_string()) { Ok(v) => v, Err(e) => { return Ok(e.to_response()); } }; let total_count = API::params_get_value(¶ms, "total_count"); let mut param = DomainListGetParam::new(); param.id = id; param.page_num = page_num; param.page_size = page_size; param.domain = domain; param.domain_filter_mode = domain_filter_mode; param.domain_type = domain_type; param.domain_group = domain_group; param.client = client; param.reply_code = reply_code; param.order = order; param.is_blocked = is_blocked; param.timestamp_after = timestamp_after; param.timestamp_before = timestamp_before; if cursor.is_some() || total_count.is_some() { let param_cursor = DomainListGetParamCursor { id: if cursor.is_some() { cursor } else { None }, total_count: if total_count.is_some() { total_count.unwrap() } else { 0 }, direction: cursor_direction, }; param.cursor = Some(param_cursor); } let data_server = this.get_data_server(); let ret = API::call_blocking(this, move || { let ret = data_server .get_domain_list(¶m) .map_err(|e| e.to_string()); if let Err(e) = ret { return Err(e.to_string()); } let ret = ret.unwrap(); return Ok(ret); }) .await; if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let ret = ret.unwrap(); if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let domain_list = ret.unwrap(); let list_count = domain_list.total_count; let mut total_page = list_count / page_size; if list_count % page_size != 0 { total_page += 1; } let total_count = domain_list.total_count; let body = api_msg_gen_domain_list(&domain_list, total_page, total_count); API::response_build(StatusCode::OK, body) } /// Delete the domain list before timestamp
/// API: DELETE /api/domain
/// parameter:
/// timestamp: u64: Unix timestamp
/// async fn api_domain_delete_list( this: Arc, _param: APIRouteParam, req: Request, ) -> Result>, HttpError> { let params = API::get_params(&req); let timestamp_before = API::params_get_value(¶ms, "timestamp_before"); if timestamp_before.is_none() { return API::response_error(StatusCode::BAD_REQUEST, "Invalid parameter."); } let timestamp_before = timestamp_before.unwrap(); let data_server = this.get_data_server(); let ret = data_server.delete_domain_before_timestamp(timestamp_before); if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } if *ret.as_ref().unwrap() == 0 { return API::response_error(StatusCode::NOT_FOUND, "Not found"); } let body = api_msg_gen_count(ret.unwrap() as i64); API::response_build(StatusCode::OK, body) } async fn api_client_get_list( this: Arc, _param: APIRouteParam, req: Request, ) -> Result>, HttpError> { let params = API::get_params(&req); let page_num = API::params_get_value_default(¶ms, "page_num", 1 as u64)?; let page_size = API::params_get_value_default(¶ms, "page_size", 10 as u64)?; if page_num == 0 || page_size == 0 { return API::response_error( StatusCode::BAD_REQUEST, "Invalid parameter: page_num or page_size", ); } let id = API::params_get_value(¶ms, "id"); let client_ip = API::params_get_value(¶ms, "client_ip"); let hostname = API::params_get_value(¶ms, "hostname"); let mac = API::params_get_value(¶ms, "mac"); let timestamp_after = API::params_get_value(¶ms, "timestamp_after"); let timestamp_before = API::params_get_value(¶ms, "timestamp_before"); let order = API::params_get_value(¶ms, "order"); let cursor = API::params_get_value(¶ms, "cursor"); let cursor_direction = match API::params_get_value_default(¶ms, "cursor_direction", "next".to_string()) { Ok(v) => v, Err(e) => { return Ok(e.to_response()); } }; let total_count = API::params_get_value(¶ms, "total_count"); let mut param = ClientListGetParam::new(); param.id = id; param.page_num = page_num; param.page_size = page_size; param.client_ip = client_ip; param.hostname = hostname; param.mac = mac; param.order = order; param.timestamp_after = timestamp_after; param.timestamp_before = timestamp_before; if cursor.is_some() || total_count.is_some() { let param_cursor = ClientListGetParamCursor { id: if cursor.is_some() { cursor } else { None }, total_count: if total_count.is_some() { total_count.unwrap() } else { 0 }, direction: cursor_direction, }; param.cursor = Some(param_cursor); } let data_server = this.get_data_server(); let ret = API::call_blocking(this, move || { let ret = data_server .get_client_list(¶m) .map_err(|e| e.to_string()); if let Err(e) = ret { return Err(e.to_string()); } let ret = ret.unwrap(); return Ok(ret); }) .await; if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let ret = ret.unwrap(); if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let client_list = ret.unwrap(); let list_count = client_list.total_count; let mut total_page = list_count / page_size; if list_count % page_size != 0 { total_page += 1; } let total_count = client_list.total_count; let body = api_msg_gen_client_list(&client_list, total_page, total_count); API::response_build(StatusCode::OK, body) } async fn api_log_stream( this: Arc, _param: APIRouteParam, mut req: Request, ) -> Result>, HttpError> { if hyper_tungstenite::is_upgrade_request(&req) { let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None) .map_err(|e| HttpError::new(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; tokio::spawn(async move { if let Err(e) = http_server_stream::serve_log_stream(this, websocket).await { dns_log!(LogLevel::DEBUG, "Error in websocket connection: {e}"); } }); Ok(response) } else { return API::response_error(StatusCode::BAD_REQUEST, "Need websocket upgrade."); } } async fn api_audit_log_stream( this: Arc, _param: APIRouteParam, mut req: Request, ) -> Result>, HttpError> { if hyper_tungstenite::is_upgrade_request(&req) { let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None) .map_err(|e| HttpError::new(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; tokio::spawn(async move { if let Err(e) = http_server_stream::serve_audit_log_stream(this, websocket).await { dns_log!(LogLevel::DEBUG, "Error in websocket connection: {e}"); } }); Ok(response) } else { return API::response_error(StatusCode::BAD_REQUEST, "Need websocket upgrade."); } } async fn api_log_set_level( this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let whole_body = String::from_utf8(_req.into_body().collect().await?.to_bytes().into())?; let level = api_msg_parse_loglevel(whole_body.as_str()); if let Err(e) = level { return API::response_error(StatusCode::BAD_REQUEST, e.to_string().as_str()); } let level = level.unwrap(); dns_log_set_level(level); let data_server = this.get_data_server(); _ = data_server.set_config("log-level", level.to_string().as_str()); API::response_build(StatusCode::NO_CONTENT, "".to_string()) } async fn api_log_get_level( _this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let level = dns_log_get_level(); let msg = api_msg_gen_loglevel(level); API::response_build(StatusCode::OK, msg) } async fn api_server_version( _this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let server_version = &smartdns::smartdns_version(); let ui_version = &smartdns::smartdns_ui_version(); let msg = api_msg_gen_version(server_version, ui_version); API::response_build(StatusCode::OK, msg) } async fn api_upstream_server_get_list( this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let data_server = this.get_data_server(); let upstream_server_list = data_server.get_upstream_server_list()?; let body = api_msg_gen_upstream_server_list(&upstream_server_list); API::response_build(StatusCode::OK, body) } async fn api_config_get_settings( this: Arc, param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let key = API::params_get_value(¶m, "key"); let data_server = this.get_data_server(); let settings = data_server.get_config_list(); if settings.is_err() { return API::response_error(StatusCode::NOT_FOUND, "Not found"); } let mut settings = settings.unwrap(); this.get_conf().settings_map().iter().for_each(|(k, v)| { if settings.get(k).is_none() { settings.insert(k.to_string(), v.to_string()); } }); let pass = settings.get(PASSWORD_CONFIG_KEY); if pass.is_some() { let pass = "********".to_string(); settings.insert(PASSWORD_CONFIG_KEY.to_string(), pass); } if key.is_some() { let key: String = key.unwrap(); let value = settings.get(key.as_str()); if value.is_none() { return API::response_error(StatusCode::NOT_FOUND, "Not found"); } let mut map = std::collections::HashMap::new(); map.insert(key, value.unwrap().clone()); let msg = api_msg_gen_key_value(&map); return API::response_build(StatusCode::OK, msg); } let msg = api_msg_gen_key_value(&settings); API::response_build(StatusCode::OK, msg) } async fn api_config_set_settings( this: Arc, _param: APIRouteParam, req: Request, ) -> Result>, HttpError> { let data_server = this.get_data_server(); let whole_body = String::from_utf8(req.into_body().collect().await?.to_bytes().into())?; let settings = api_msg_parse_key_value(whole_body.as_str()); if let Err(e) = settings { return API::response_error(StatusCode::BAD_REQUEST, e.to_string().as_str()); } let settings = settings.unwrap(); for (key, value) in settings { if key == PASSWORD_CONFIG_KEY { continue; } let ret = data_server.set_config(key.as_str(), value.as_str()); if let Err(e) = ret { return API::response_error( StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str(), ); } } API::response_build(StatusCode::NO_CONTENT, "".to_string()) } async fn api_stats_get_top_client( this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let data_server = this.get_data_server(); let params = API::get_params(&_req); let count = API::params_get_value(¶ms, "count"); let ret = API::call_blocking(this, move || { let ret = data_server.get_top_client_top_list(count); if let Err(e) = ret { return Err(e.to_string()); } let ret = ret.unwrap(); return Ok(ret); }) .await; if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let ret = ret.unwrap(); if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let body = api_msg_gen_top_client_list(&ret.unwrap()); API::response_build(StatusCode::OK, body) } async fn api_stats_get_top_domain( this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let data_server = this.get_data_server(); let params = API::get_params(&_req); let count = API::params_get_value(¶ms, "count"); let ret = API::call_blocking(this, move || { let ret = data_server.get_top_domain_top_list(count); if let Err(e) = ret { return Err(e.to_string()); } let ret = ret.unwrap(); return Ok(ret); }) .await; if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let ret = ret.unwrap(); if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let body = api_msg_gen_top_domain_list(&ret.unwrap()); API::response_build(StatusCode::OK, body) } async fn api_stats_get_metrics( this: Arc, _param: APIRouteParam, mut req: Request, ) -> Result>, HttpError> { let data_server = this.get_data_server(); if hyper_tungstenite::is_upgrade_request(&req) { let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None) .map_err(|e| HttpError::new(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; tokio::spawn(async move { if let Err(e) = http_server_stream::serve_metrics(data_server, websocket).await { dns_log!(LogLevel::DEBUG, "Error in websocket connection: {e}"); } }); Ok(response) } else { let metrics = data_server.get_metrics()?; let body = api_msg_gen_metrics_data(&metrics); return API::response_build(StatusCode::OK, body); } } async fn api_stats_get_overview( this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let data_server = this.get_data_server(); let overview = data_server.get_overview()?; let body = api_msg_gen_stats_overview(&overview); return API::response_build(StatusCode::OK, body); } async fn api_stats_refresh( this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let data_server = this.get_data_server(); let ret = API::call_blocking(this, move || -> Result<(), String> { data_server.get_stat().refresh(); Ok(()) }) .await; if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } API::response_build(StatusCode::NO_CONTENT, "".to_string()) } async fn api_stats_get_daily_query_count( this: Arc, param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let data_server = this.get_data_server(); let past_days = API::params_get_value(¶m, "past_days"); let ret = API::call_blocking(this, move || { let ret = data_server.get_daily_query_count(past_days); if let Err(e) = ret { return Err(e.to_string()); } let ret = ret.unwrap(); return Ok(ret); }) .await; if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let ret = ret.unwrap(); if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let body = api_msg_gen_daily_query_count(&ret.unwrap()); API::response_build(StatusCode::OK, body) } async fn api_stats_get_hourly_query_count( this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let params = API::get_params(&_req); let past_hours = API::params_get_value(¶ms, "past_hours"); let data_server = this.get_data_server(); let ret = API::call_blocking(this, move || { let ret = data_server.get_hourly_query_count(past_hours); if let Err(e) = ret { return Err(e.to_string()); } let ret = ret.unwrap(); return Ok(ret); }) .await; if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let ret = ret.unwrap(); if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let body = api_msg_gen_hourly_query_count(&ret.unwrap()); API::response_build(StatusCode::OK, body) } async fn api_whois( this: Arc, _param: APIRouteParam, _req: Request, ) -> Result>, HttpError> { let params = API::get_params(&_req); let domain = API::params_get_value(¶ms, "domain"); if domain.is_none() { return API::response_error(StatusCode::BAD_REQUEST, "Invalid parameter."); } let domain: String = domain.unwrap(); let data_server = this.get_data_server(); let ret = data_server.whois(domain.as_str()).await; if let Err(e) = ret { return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); } let body = api_msg_gen_whois_info(&ret.unwrap()); API::response_build(StatusCode::OK, body) } async fn api_tool_term( this: Arc, _param: APIRouteParam, mut req: Request, ) -> Result>, HttpError> { if this.get_conf().enable_terminal != true { return API::response_error(StatusCode::FORBIDDEN, "Terminal is disabled."); } if hyper_tungstenite::is_upgrade_request(&req) { let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None) .map_err(|e| HttpError::new(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; tokio::spawn(async move { if let Err(e) = http_server_stream::serve_term(websocket).await { dns_log!(LogLevel::DEBUG, "Error in websocket connection: {e}"); } }); Ok(response) } else { return API::response_error(StatusCode::BAD_REQUEST, "Need websocket upgrade."); } } async fn call_blocking( this: Arc, func: F, ) -> Result> where F: FnOnce() -> R + Send + 'static, R: Send + 'static, { let rt = this.get_data_server().get_plugin().unwrap().get_runtime(); let ret = rt.spawn_blocking(move || -> R { return func(); }); let ret = ret.await; if ret.is_err() { return Err(Box::new(ret.err().unwrap())); } let ret = ret.unwrap(); return Ok(ret); } } ================================================ FILE: plugin/smartdns-ui/src/http_server_stream.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 futures::sink::SinkExt; use futures::stream::StreamExt; use hyper_tungstenite::tungstenite::protocol::frame::coding::CloseCode; use hyper_tungstenite::tungstenite::protocol::CloseFrame; use std::os::fd::AsRawFd; use std::sync::Arc; use std::time::Duration; use tokio::time::{interval_at, Instant}; use tokio_fd::AsyncFd; use crate::smartdns::*; use hyper_tungstenite::{tungstenite, HyperWebsocket}; use nix::errno::Errno; use nix::libc::*; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tungstenite::Message; use crate::data_server::DataServer; use crate::dns_log; use crate::http_api_msg::api_msg_gen_metrics_data; use crate::http_server::HttpServer; use crate::smartdns::LogLevel; type Error = Box; const LOG_CONTROL_MESSAGE_TYPE: u8 = 1; const LOG_CONTROL_PAUSE: u8 = 1; const LOG_CONTROL_RESUME: u8 = 2; const LOG_CONTROL_LOGLEVEL: u8 = 3; struct LogLevelGuard { old_log_level: LogLevel, } impl Drop for LogLevelGuard { fn drop(&mut self) { dns_log_set_level(self.old_log_level); } } impl LogLevelGuard { fn new() -> Self { let old_log_level = dns_log_get_level(); LogLevelGuard { old_log_level } } } pub async fn serve_log_stream( http_server: Arc, websocket: HyperWebsocket, ) -> Result<(), Error> { let mut websocket = websocket.await?; let mut is_pause = false; let data_server = http_server.get_data_server(); let mut log_stream = data_server.get_log_stream().await; let _log_guard = LogLevelGuard::new(); loop { tokio::select! { msg = log_stream.recv() => { if is_pause { continue; } match msg { Some(msg) => { let mut binary_msg = Vec::with_capacity(2 + msg.msg.len()); binary_msg.push(0); binary_msg.push(msg.level as u8); binary_msg.extend_from_slice(msg.msg.as_bytes()); let msg = Message::Binary(binary_msg.into()); websocket.send(msg).await?; } None => { websocket.send(Message::Close(None)).await?; break; } } } msg = websocket.next() => { let message = msg.ok_or("websocket closed")??; match message { Message::Text(_msg) => {} Message::Binary(msg) => { if msg.len() == 0 { continue; } let msg_type = msg[0]; match msg_type { LOG_CONTROL_MESSAGE_TYPE => { if msg.len() < 2 { continue; } let control_type = msg[1]; match control_type { LOG_CONTROL_PAUSE => { is_pause = true; continue; } LOG_CONTROL_RESUME => { is_pause = false; continue; } LOG_CONTROL_LOGLEVEL => { if msg.len() < 6 { continue; } let level_msg = &msg[2..2 + msg.len() - 2]; let str_log_level = std::str::from_utf8(level_msg); if str_log_level.is_err() { continue; } let str_log_level = str_log_level.unwrap(); if str_log_level.len() == 0 { continue; } let str_log_level = str_log_level.to_lowercase(); let str_log_level = str_log_level.as_str(); let log_level = str_log_level.try_into(); if log_level.is_err() { continue; } let log_level = log_level.unwrap(); dns_log_set_level(log_level); } _ => { continue; } } } _ => {} } } Message::Ping(_msg) => {} Message::Pong(_msg) => {} Message::Close(_msg) => { websocket.send(Message::Close(None)).await?; break; } Message::Frame(_msg) => { unreachable!(); } } } } } Ok(()) } pub async fn serve_audit_log_stream( http_server: Arc, websocket: HyperWebsocket, ) -> Result<(), Error> { let mut websocket = websocket.await?; let mut is_pause = false; let data_server = http_server.get_data_server(); let mut log_stream = data_server.get_audit_log_stream().await; if dns_audit_log_enabled() == false { let reason = "Audit log is not enabled, please set `audit-enable` to `yes` in smartdns config file."; let close_msg = CloseFrame { code: CloseCode::Bad(4003), reason: reason.into(), }; websocket.send(Message::Close(Some(close_msg))).await?; return Ok(()); } loop { tokio::select! { msg = log_stream.recv() => { if is_pause { continue; } match msg { Some(msg) => { let mut binary_msg = Vec::with_capacity(1 + msg.msg.len()); binary_msg.push(0); binary_msg.extend_from_slice(msg.msg.as_bytes()); let msg = Message::Binary(binary_msg.into()); websocket.send(msg).await?; } None => { websocket.send(Message::Close(None)).await?; break; } } } msg = websocket.next() => { let message = msg.ok_or("websocket closed")??; match message { Message::Text(_msg) => {} Message::Binary(msg) => { if msg.len() == 0 { continue; } let msg_type = msg[0]; match msg_type { LOG_CONTROL_MESSAGE_TYPE => { if msg.len() < 2 { continue; } let control_type = msg[1]; match control_type { LOG_CONTROL_PAUSE => { is_pause = true; continue; } LOG_CONTROL_RESUME => { is_pause = false; continue; } _ => { continue; } } } _ => {} } } Message::Ping(_msg) => {} Message::Pong(_msg) => {} Message::Close(_msg) => { websocket.send(Message::Close(None)).await?; break; } Message::Frame(_msg) => { unreachable!(); } } } } } Ok(()) } pub async fn serve_metrics( data_server: Arc, websocket: HyperWebsocket, ) -> Result<(), Error> { let mut websocket = websocket.await?; let mut second_timer = interval_at(Instant::now(), Duration::from_secs(1)); loop { tokio::select! { _ = second_timer.tick() => { let metrics = data_server.get_metrics(); match metrics { Ok(metrics) => { let data_server = api_msg_gen_metrics_data(&metrics); let msg = Message::Text(data_server.into()); websocket.send(msg).await?; } Err(e) => { let msg = Message::Text(format!("{{\"error\": \"{}\"}}", e).into()); websocket.send(msg).await?; } } } msg = websocket.next() => { let message = msg.ok_or("websocket closed")??; match message { Message::Text(_msg) => {} Message::Binary(_msg) => {} Message::Ping(_msg) => {} Message::Pong(_msg) => {} Message::Close(_msg) => { websocket.send(Message::Close(None)).await?; break; } Message::Frame(_msg) => { unreachable!(); } } } } } Ok(()) } enum TermMessageType { Data, Err, Resize, Pause, Resume, } impl TryFrom for TermMessageType { type Error = (); fn try_from(value: u8) -> Result { match value { 0 => Ok(TermMessageType::Data), 1 => Ok(TermMessageType::Err), 2 => Ok(TermMessageType::Resize), 3 => Ok(TermMessageType::Pause), 4 => Ok(TermMessageType::Resume), _ => Err(()), } } } #[cfg(target_os = "linux")] pub async fn serve_term(websocket: HyperWebsocket) -> Result<(), Error> { type WsType = hyper_tungstenite::WebSocketStream>; let mut websocket = websocket.await?; let (pid, asyncfd) = unsafe { let mut fd_master: std::os::fd::RawFd = 0; let mut ws = winsize { ws_row: 24, ws_col: 80, ws_xpixel: 0, ws_ypixel: 0, }; let pid = forkpty( &mut fd_master, std::ptr::null_mut(), std::ptr::null_mut(), &mut ws, ); if pid < 0 { dns_log!(LogLevel::ERROR, "forkpty failed, error: {}", Errno::last()); return Err(format!("forkpty failed, error: {}", Errno::last()).into()); } if pid == 0 { let _ = ioctl(0, TIOCSCTTY, 1); for i in 3..1024 { close(i); } use std::ffi::CString; std::env::set_var("TERM", "xterm-256color"); let find_cmd = |cmd: &str| -> Result> { let env_path = std::env::var("PATH")?; let paths = env_path.split(':'); for path in paths { let cmd_path = format!("{}/{}", path, cmd); if std::fs::metadata(&cmd_path).is_ok() { return Ok(cmd_path); } } Err(format!("command not found {}", cmd).into()) }; let su_path = find_cmd("su"); let login_path = find_cmd("login"); let mut err = ENOENT; if su_path.is_ok() && (login_path.is_err() || geteuid() != 0) { let uid = getuid(); let pw = getpwuid(uid); if pw.is_null() { println!("getpwuid failed"); _exit(1); } let arg0 = CString::new("su").unwrap(); let arg1 = CString::new("-").unwrap(); let arg2 = (*pw).pw_name; let login_message = format!("Login as {}", std::ffi::CStr::from_ptr(arg2).to_str()?); println!("{}", login_message); let cmd_path = CString::new(su_path.unwrap()).unwrap(); let args = [arg0.as_ptr(), arg1.as_ptr(), arg2, std::ptr::null()]; let ret = execv(cmd_path.as_ptr(), args.as_ptr()); if ret < 0 { err = Errno::last_raw(); } println!("Please install `su` and add current user to sudoers"); } else if login_path.is_ok() { if geteuid() != 0 { println!("Login must be run as root, please run smartdns as root"); _exit(1); } let arg0 = CString::new("login").unwrap(); let cmd_path = CString::new(login_path.unwrap()).unwrap(); let args = [arg0.as_ptr(), std::ptr::null()]; let ret = execv(cmd_path.as_ptr(), args.as_ptr()); if ret < 0 { err = Errno::last_raw(); } println!("Please install `login` and run as root"); } else { println!("No su or login found, please install one of them"); } println!("Failed to execute `su` or `login`, code: {}", err); _exit(1); } (pid, AsyncFd::try_from(fd_master)) }; if let Err(e) = asyncfd { if pid > 0 { unsafe { let _ = kill(pid, SIGKILL); let _ = waitpid(pid, std::ptr::null_mut(), 0); } } return Err(e.into()); } let send_error_msg = |ws: &mut WsType, msg: &str| { let mut buf = [0u8; 4096]; buf[0] = TermMessageType::Err as u8; buf[1..msg.len() + 1].copy_from_slice(msg.as_bytes()); let msg = Message::Binary(buf[..msg.len() + 1].to_vec().into()); let _ = ws.send(msg); let msg = Message::Close(None); let _ = ws.send(msg); }; let mut asyncfd = asyncfd.unwrap(); let mut is_pause = false; loop { let mut buf = [0u8; 4096]; let (data_type, data_buf) = buf.split_at_mut(1); let data_len; tokio::select! { n = asyncfd.read(data_buf) => { match n { Ok(n) => { if n == 0 { websocket.send(Message::Close(None)).await?; dns_log!(LogLevel::DEBUG, "EOF"); break; } if is_pause { continue; } data_len = n + 1; data_type[0] = TermMessageType::Data as u8; let msg = Message::Binary(buf[..data_len].to_vec().into()); websocket.send(msg).await?; } Err(e) => { send_error_msg(&mut websocket, e.to_string().as_str()); dns_log!(LogLevel::DEBUG, "Error: {}", e.to_string().as_str()); break; } } } msg = websocket.next() => { let message = msg.ok_or("websocket closed")??; match message { Message::Text(msg) => { asyncfd.write(msg.as_bytes()).await?; } Message::Binary(msg) => { if msg.len() == 0 { continue; } let msg_type = TermMessageType::try_from(msg[0]); if msg_type.is_err() { send_error_msg(&mut websocket, "invalid message type"); break; } let msg_type = msg_type.unwrap(); let msg = &msg[1..]; match msg_type { TermMessageType::Resize => { let ws = winsize { ws_col: u16::from_be_bytes(msg[0..2].try_into().unwrap()), ws_row: u16::from_be_bytes(msg[2..4].try_into().unwrap()), ws_xpixel: 0, ws_ypixel: 0, }; unsafe { let _ = ioctl(asyncfd.as_raw_fd(), TIOCSWINSZ, &ws); } } TermMessageType::Pause => { is_pause = true; } TermMessageType::Resume => { is_pause = false; } TermMessageType::Data => { asyncfd.write(msg).await?; } _ => { continue; } } } Message::Ping(_msg) => {} Message::Pong(_msg) => {} Message::Close(_msg) => { dns_log!(LogLevel::DEBUG, "Peer term closed"); break; } Message::Frame(_msg) => { unreachable!(); } } } } } unsafe { let fd = asyncfd.as_raw_fd(); if fd > 0 { let _ = close(fd); } let _ = kill(pid, SIGKILL); let _ = waitpid(pid, std::ptr::null_mut(), 0); } Ok(()) } ================================================ FILE: plugin/smartdns-ui/src/lib.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 . */ pub mod data_server; pub mod data_stats; pub mod data_upstream_server; pub mod db; pub mod http_api_msg; pub mod http_error; pub mod http_jwt; pub mod http_server; pub mod http_server_api; pub mod http_server_stream; pub mod plugin; pub mod server_log; pub mod smartdns; pub mod utils; pub mod whois; use ctor::ctor; use ctor::dtor; #[cfg(not(test))] use plugin::*; use smartdns::*; #[cfg(not(test))] fn lib_init_ops() { let ops: Box = Box::new(SmartdnsPluginImpl::new()); unsafe { let plugin_addr = std::ptr::addr_of_mut!(PLUGIN); (*plugin_addr).set_operation(ops); } } #[cfg(not(test))] fn lib_deinit_ops() { unsafe { let plugin_addr = std::ptr::addr_of_mut!(PLUGIN); (*plugin_addr).clear_operation(); } } #[cfg(test)] fn lib_init_smartdns_lib() { smartdns::dns_log_set_level(LogLevel::DEBUG); } #[ctor] fn lib_init() { #[cfg(not(test))] lib_init_ops(); #[cfg(test)] lib_init_smartdns_lib(); } #[dtor] fn lib_deinit() { #[cfg(not(test))] lib_deinit_ops(); } ================================================ FILE: plugin/smartdns-ui/src/plugin.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_server::*; use crate::dns_log; use crate::http_server::*; use crate::smartdns::*; use getopts::Options; use std::error::Error; use std::sync::Arc; use std::sync::Mutex; use tokio::runtime::Builder; use tokio::runtime::Runtime; pub struct SmartdnsPlugin { http_server_ctl: Arc, http_conf: Mutex, data_server_ctl: Arc, data_conf: Mutex, runtime: Arc, is_start: Mutex, } impl SmartdnsPlugin { pub fn new() -> Arc { let rt = Builder::new_multi_thread() .enable_all() .thread_name("smartdns-ui") .thread_keep_alive(tokio::time::Duration::from_secs(30)) .build() .unwrap(); let plugin = Arc::new(SmartdnsPlugin { http_server_ctl: Arc::new(HttpServerControl::new()), http_conf: Mutex::new(HttpServerConfig::new()), data_server_ctl: Arc::new(DataServerControl::new()), data_conf: Mutex::new(DataServerConfig::new()), runtime: Arc::new(rt), is_start: Mutex::new(false), }); plugin.http_server_ctl.set_plugin(plugin.clone()); plugin.data_server_ctl.set_plugin(plugin.clone()); plugin } pub fn get_runtime(&self) -> Arc { self.runtime.clone() } pub fn get_http_server(&self) -> Arc { self.http_server_ctl.get_http_server() } pub fn get_data_server(&self) -> Arc { self.data_server_ctl.get_data_server() } fn parser_args(&self, args: &Vec) -> Result<(), Box> { let mut opts = Options::new(); opts.optopt("i", "ip", "http ip", "IP"); opts.optopt("r", "www-root", "http www root", "PATH"); opts.optopt("", "data-dir", "http data dir", "PATH"); opts.optopt("", "token-expire", "http token expire time", "TIME"); if args.len() <= 0 { return Ok(()); } let matches = match opts.parse(&args[1..]) { Ok(m) => m, Err(f) => { return Err(Box::new(f)); } }; let mut http_conf = self.http_conf.lock().unwrap(); let mut data_conf = self.data_conf.lock().unwrap(); let www_root = Plugin::dns_conf_plugin_config("smartdns-ui.www-root"); if let Some(www_root) = www_root { http_conf.http_root = smartdns_conf_get_conf_fullpath(&www_root); } let ip = Plugin::dns_conf_plugin_config("smartdns-ui.ip"); if let Some(ip) = ip { http_conf.http_ip = ip; } if let Some(ip) = matches.opt_str("i") { http_conf.http_ip = ip; } if let Some(root) = matches.opt_str("r") { http_conf.http_root = root; } let mut token_expire = Plugin::dns_conf_plugin_config("smartdns-ui.token-expire"); if token_expire.is_none() { token_expire = matches.opt_str("token-expire"); } if let Some(token_expire) = token_expire { let v = token_expire.parse::(); if let Err(e) = v { dns_log!( LogLevel::ERROR, "parse token expire time error: {}", e.to_string() ); return Err(Box::new(e)); } http_conf.token_expired_time = v.unwrap(); } if let Some(data_dir) = matches.opt_str("data-dir") { data_conf.data_path = data_dir; } Ok(()) } pub fn load_config(&self) -> Result<(), Box> { let data_server = self.get_data_server(); self.data_conf .lock() .unwrap() .load_config(data_server.clone())?; self.http_conf .lock() .unwrap() .load_config(data_server.clone())?; Ok(()) } pub fn start(&self, args: &Vec) -> Result<(), Box> { dns_log!(LogLevel::INFO, "start smartdns-ui server."); let mut is_start = self.is_start.lock().unwrap(); *is_start = true; self.parser_args(args)?; self.load_config()?; self.data_server_ctl .init_db(&self.data_conf.lock().unwrap())?; self.load_config()?; self.data_server_ctl.start_data_server()?; let http_conf = self.http_conf.lock().unwrap().clone(); self.http_server_ctl.start_http_server(&http_conf)?; Ok(()) } pub fn stop(&self) { let mut is_start = self.is_start.lock().unwrap(); if !*is_start { return; } *is_start = false; dns_log!(LogLevel::INFO, "stop smartdns-ui server."); self.http_server_ctl.stop_http_server(); self.data_server_ctl.stop_data_server(); } pub fn query_complete(&self, request: Box) -> Result<(), Box> { let ret = self.data_server_ctl.send_request(request); if let Err(e) = ret { dns_log!(LogLevel::DEBUG, "send request error: {}", e.to_string()); return Err(e); } Ok(()) } pub fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32) { self.data_server_ctl.server_log(level, msg, msg_len); } pub fn server_audit_log(&self, msg: &str, msg_len: i32) { self.data_server_ctl.server_audit_log(msg, msg_len); } } impl Drop for SmartdnsPlugin { fn drop(&mut self) { self.stop(); } } pub struct SmartdnsPluginImpl { plugin: Arc, } impl SmartdnsPluginImpl { pub fn new() -> Self { SmartdnsPluginImpl { plugin: SmartdnsPlugin::new(), } } } impl Drop for SmartdnsPluginImpl { fn drop(&mut self) { self.plugin.stop(); } } impl SmartdnsOperations for SmartdnsPluginImpl { fn server_query_complete(&self, request: Box) { let _ = self.plugin.query_complete(request); } fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32) { self.plugin.server_log(level, msg, msg_len); } fn server_audit_log(&self, msg: &str, msg_len: i32) { self.plugin.server_audit_log(msg, msg_len); } fn server_init(&mut self, args: &Vec) -> Result<(), Box> { self.plugin.start(args) } fn server_exit(&mut self) { self.plugin.stop(); } } ================================================ FILE: plugin/smartdns-ui/src/server_log.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 tokio::sync::{mpsc, RwLock}; use crate::LogLevel; #[derive(Clone)] pub struct ServerLogMsg { pub level: LogLevel, pub msg: String, pub len: i32, } pub struct ServerLog { streams: RwLock>>, } impl ServerLog { pub fn new() -> Self { ServerLog { streams: RwLock::new(Vec::new()), } } pub async fn get_log_stream(&self) -> mpsc::Receiver { let (tx, rx) = mpsc::channel(4096); self.streams.write().await.push(tx); rx } pub fn dispatch_log(&self, level: LogLevel, msg: &str, len: i32) { let mut remove_list = Vec::new(); { let streams = self.streams.blocking_read(); if streams.len() == 0 { return; } let msg = ServerLogMsg { level, msg: msg.to_string(), len, }; for (i, stream) in streams.iter().enumerate() { let ret = stream.try_send(msg.clone()); if let Err(e) = ret { match e { mpsc::error::TrySendError::Full(_) => {} mpsc::error::TrySendError::Closed(_) => { remove_list.push(i); } } } } } if remove_list.len() > 0 { let mut streams = self.streams.blocking_write(); for i in remove_list.iter().rev() { streams.remove(*i); } } } } #[derive(Clone)] pub struct ServerAuditLogMsg { pub msg: String, pub len: i32, } pub struct ServerAuditLog { streams: RwLock>>, } impl ServerAuditLog { pub fn new() -> Self { ServerAuditLog { streams: RwLock::new(Vec::new()), } } pub async fn get_audit_log_stream(&self) -> mpsc::Receiver { let (tx, rx) = mpsc::channel(4096); self.streams.write().await.push(tx); rx } pub fn dispatch_audit_log(&self, msg: &str, len: i32) { let mut remove_list = Vec::new(); { let streams = self.streams.blocking_read(); if streams.len() == 0 { return; } let msg = ServerAuditLogMsg { msg: msg.to_string(), len, }; for (i, stream) in streams.iter().enumerate() { let ret = stream.try_send(msg.clone()); if let Err(e) = ret { match e { mpsc::error::TrySendError::Full(_) => {} mpsc::error::TrySendError::Closed(_) => { remove_list.push(i); } } } } } if remove_list.len() > 0 { let mut streams = self.streams.blocking_write(); for i in remove_list.iter().rev() { streams.remove(*i); } } } } ================================================ FILE: plugin/smartdns-ui/src/smartdns.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 . */ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(dead_code)] #![allow(unused_imports)] #![allow(improper_ctypes)] pub mod smartdns_c { include!(concat!(env!("OUT_DIR"), "/smartdns_bindings.rs")); } use std::error::Error; use std::ffi::CString; use std::fmt; use std::os::raw::*; #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq)] #[allow(dead_code)] pub enum LogLevel { DEBUG = 0, INFO = 1, NOTICE = 2, WARN = 3, ERROR = 4, FATAL = 5, } impl From for u32 { fn from(level: LogLevel) -> u32 { level as u32 } } impl ToString for LogLevel { fn to_string(&self) -> String { match self { LogLevel::DEBUG => "debug".to_string(), LogLevel::INFO => "info".to_string(), LogLevel::NOTICE => "notice".to_string(), LogLevel::WARN => "warn".to_string(), LogLevel::ERROR => "error".to_string(), LogLevel::FATAL => "fatal".to_string(), } } } impl TryFrom for LogLevel { type Error = (); fn try_from(value: u32) -> Result { match value { 0 => Ok(LogLevel::DEBUG), 1 => Ok(LogLevel::INFO), 2 => Ok(LogLevel::NOTICE), 3 => Ok(LogLevel::WARN), 4 => Ok(LogLevel::ERROR), 5 => Ok(LogLevel::FATAL), _ => Err(()), } } } impl TryFrom<&str> for LogLevel { type Error = (); fn try_from(value: &str) -> Result { match value.to_lowercase().as_str() { "debug" => Ok(LogLevel::DEBUG), "info" => Ok(LogLevel::INFO), "notice" => Ok(LogLevel::NOTICE), "warn" => Ok(LogLevel::WARN), "error" => Ok(LogLevel::ERROR), "fatal" => Ok(LogLevel::FATAL), _ => Err(()), } } } impl TryFrom for LogLevel { type Error = (); fn try_from(value: String) -> Result { LogLevel::try_from(value.as_str()) } } #[derive(Debug, Clone)] pub enum DnsServerType { SERVER_UDP, SERVER_TCP, SERVER_TLS, SERVER_HTTPS, SERVER_QUIC, SERVER_HTTP3, SERVER_MDNS, SERVER_UNKNOWN, } impl From for DnsServerType { fn from(t: u32) -> DnsServerType { match t { 0 => DnsServerType::SERVER_UDP, 1 => DnsServerType::SERVER_TCP, 2 => DnsServerType::SERVER_TLS, 3 => DnsServerType::SERVER_HTTPS, 4 => DnsServerType::SERVER_QUIC, 5 => DnsServerType::SERVER_HTTP3, 6 => DnsServerType::SERVER_MDNS, _ => DnsServerType::SERVER_UNKNOWN, } } } impl std::str::FromStr for DnsServerType { type Err = String; fn from_str(t: &str) -> Result { match t { "udp" => Ok(DnsServerType::SERVER_UDP), "tcp" => Ok(DnsServerType::SERVER_TCP), "tls" => Ok(DnsServerType::SERVER_TLS), "https" => Ok(DnsServerType::SERVER_HTTPS), "quic" => Ok(DnsServerType::SERVER_QUIC), "http3" => Ok(DnsServerType::SERVER_HTTP3), "mdns" => Ok(DnsServerType::SERVER_MDNS), _ => Err("unknown".to_string()), } } } impl ToString for DnsServerType { fn to_string(&self) -> String { match self { DnsServerType::SERVER_UDP => "udp".to_string(), DnsServerType::SERVER_TCP => "tcp".to_string(), DnsServerType::SERVER_TLS => "tls".to_string(), DnsServerType::SERVER_HTTPS => "https".to_string(), DnsServerType::SERVER_QUIC => "quic".to_string(), DnsServerType::SERVER_HTTP3 => "http3".to_string(), DnsServerType::SERVER_MDNS => "mdns".to_string(), DnsServerType::SERVER_UNKNOWN => "unknown".to_string(), } } } #[derive(Debug, Clone)] pub enum DnsServerTrustStatus { TRUST_UNKNOW, TRUST_NOT_APPLICABLE, TRUST_VERIFY_FAILED, TRUST_INSECURE, TRUST_SECURE, } impl From for DnsServerTrustStatus { fn from(t: u32) -> DnsServerTrustStatus { match t { 0 => DnsServerTrustStatus::TRUST_UNKNOW, 1 => DnsServerTrustStatus::TRUST_NOT_APPLICABLE, 2 => DnsServerTrustStatus::TRUST_VERIFY_FAILED, 3 => DnsServerTrustStatus::TRUST_INSECURE, 4 => DnsServerTrustStatus::TRUST_SECURE, _ => DnsServerTrustStatus::TRUST_UNKNOW, } } } impl ToString for DnsServerTrustStatus { fn to_string(&self) -> String { match self { DnsServerTrustStatus::TRUST_UNKNOW => "Unknown".to_string(), DnsServerTrustStatus::TRUST_NOT_APPLICABLE => "Not Applicable".to_string(), DnsServerTrustStatus::TRUST_VERIFY_FAILED => "Verify Failed".to_string(), DnsServerTrustStatus::TRUST_INSECURE => "Insecure".to_string(), DnsServerTrustStatus::TRUST_SECURE => "Secure".to_string(), } } } #[macro_export] macro_rules! dns_log { ($level:expr, $($arg:tt)*) => { if $crate::smartdns::dns_can_log($level) { $crate::smartdns::dns_log_out($level, file!(), line!(), &format!($($arg)*)); } }; } pub fn dns_can_log(level: LogLevel) -> bool { unsafe { smartdns_c::smartdns_plugin_can_log(level as u32) != 0 } } pub fn dns_log_set_level(level: LogLevel) { unsafe { smartdns_c::smartdns_plugin_log_setlevel(level as u32); } } pub fn dns_log_get_level() -> LogLevel { unsafe { let leve = smartdns_c::smartdns_plugin_log_getlevel(); LogLevel::try_from(leve as u32).unwrap() } } pub fn dns_audit_log_enabled() -> bool { unsafe { smartdns_c::smartdns_plugin_is_audit_enabled() != 0 } } pub fn dns_log_out(level: LogLevel, file: &str, line: u32, message: &str) { let filename_only = std::path::Path::new(file) .file_name() .and_then(|s| s.to_str()) .unwrap(); let file_cstring = CString::new(filename_only).expect("Failed to convert to CString"); let message_cstring = CString::new(message).expect("Failed to convert to CString"); unsafe { smartdns_c::smartdns_plugin_log( level as u32, file_cstring.as_ptr(), line as i32, std::ptr::null(), message_cstring.as_ptr(), ); } } pub fn smartdns_version() -> String { unsafe { let version = smartdns_c::smartdns_version(); std::ffi::CStr::from_ptr(version) .to_string_lossy() .into_owned() } } pub fn smartdns_ui_version() -> String { let mut ver = env!("CARGO_PKG_VERSION").to_string(); if env!("GIT_VERSION").is_empty() { return ver; } ver.push_str(" ("); ver.push_str(env!("GIT_VERSION")); ver.push_str(")"); ver } pub fn smartdns_get_server_name() -> String { unsafe { let mut buffer = [0u8; 4096]; smartdns_c::dns_server_get_server_name( buffer.as_mut_ptr() as *mut c_char, buffer.len() as i32, ); let srv_name = std::ffi::CStr::from_ptr(buffer.as_ptr() as *const c_char) .to_string_lossy() .into_owned(); srv_name } } pub fn smartdns_server_run(file: &str) -> Result<(), Box> { let file = CString::new(file).expect("Failed to convert to CString"); let ret: i32; unsafe { ret = smartdns_c::smartdns_server_run(file.as_ptr()); }; if ret != 0 { return Err("smartdns server run error".into()); } Ok(()) } pub fn smartdns_enable_update_neighbour(enable: bool) { unsafe { if enable { smartdns_c::dns_server_enable_update_neighbor_cache(1); } else { smartdns_c::dns_server_enable_update_neighbor_cache(0); } } } pub fn smartdns_conf_get_conf_fullpath(path: &str) -> String { let path = CString::new(path).expect("Failed to convert to CString"); unsafe { let mut buffer = [0u8; 4096]; smartdns_c::conf_get_conf_fullpath( path.as_ptr(), buffer.as_mut_ptr() as *mut c_char, buffer.len().try_into().unwrap(), ); let conf_fullpath = std::ffi::CStr::from_ptr(buffer.as_ptr() as *const c_char) .to_string_lossy() .into_owned(); conf_fullpath } } pub fn smartdns_server_stop() { unsafe { smartdns_c::smartdns_server_stop(); } } pub fn get_utc_time_ms() -> u64 { unsafe { smartdns_c::get_utc_time_ms() } } static SMARTDNS_OPS: smartdns_c::smartdns_operations = smartdns_c::smartdns_operations { server_recv: None, server_query_complete: Some(dns_request_complete), server_log: Some(dns_server_log), server_audit_log: Some(dns_server_audit_log), }; #[no_mangle] extern "C" fn dns_request_complete(request: *mut smartdns_c::dns_request) { unsafe { let plugin_addr = std::ptr::addr_of_mut!(PLUGIN); let ops = (*plugin_addr).ops.as_ref(); if let None = ops { return; } let ops = ops.unwrap(); let req = DnsRequest_C::new(request); ops.server_query_complete(Box::new(req)); } } #[no_mangle] extern "C" fn dns_server_log( level: smartdns_c::smartdns_log_level, msg: *const c_char, msg_len: i32, ) { unsafe { let plugin_addr = std::ptr::addr_of_mut!(PLUGIN); let ops = (*plugin_addr).ops.as_ref(); if let None = ops { return; } let raw_msg = std::slice::from_raw_parts(msg as *const u8, msg_len as usize + 1); let msg = std::ffi::CStr::from_bytes_with_nul_unchecked(raw_msg) .to_string_lossy() .into_owned(); let level = LogLevel::try_from(level as u32).unwrap(); let ops = ops.unwrap(); ops.server_log(level, msg.as_str(), msg_len as i32); } } #[no_mangle] extern "C" fn dns_server_audit_log(msg: *const c_char, msg_len: i32) { unsafe { let plugin_addr = std::ptr::addr_of_mut!(PLUGIN); let ops = (*plugin_addr).ops.as_ref(); if let None = ops { return; } let raw_msg = std::slice::from_raw_parts(msg as *const u8, msg_len as usize + 1); let msg = std::ffi::CStr::from_bytes_with_nul_unchecked(raw_msg) .to_string_lossy() .into_owned(); let ops = ops.unwrap(); ops.server_audit_log(msg.as_str(), msg_len as i32); } } #[no_mangle] extern "C" fn dns_plugin_init(plugin: *mut smartdns_c::dns_plugin) -> i32 { unsafe { let plugin_addr = std::ptr::addr_of_mut!(PLUGIN); (*plugin_addr).parser_args(plugin).unwrap(); smartdns_c::smartdns_operations_register(&SMARTDNS_OPS); let ret = (*plugin_addr) .ops .as_mut() .unwrap() .server_init((*plugin_addr).get_args()); if let Err(e) = ret { dns_log!(LogLevel::ERROR, "{}", e.to_string()); dns_log!(LogLevel::ERROR, "server init failed."); return -1; } } return 0; } #[no_mangle] extern "C" fn dns_plugin_exit(_plugin: *mut smartdns_c::dns_plugin) -> i32 { unsafe { let plugin_addr = std::ptr::addr_of_mut!(PLUGIN); smartdns_c::smartdns_operations_unregister(&SMARTDNS_OPS); (*plugin_addr).ops.as_mut().unwrap().server_exit(); } return 0; } #[no_mangle] extern "C" fn dns_plugin_api_version() -> u32 { smartdns_c::SMARTDNS_PLUGIN_API_VERSION } pub trait DnsRequest: Send + Sync { fn get_group_name(&self) -> String; fn get_domain(&self) -> String; fn get_qtype(&self) -> u32; fn get_qclass(&self) -> i32; fn get_id(&self) -> u16; fn get_rcode(&self) -> u16; fn get_query_time(&self) -> i32; fn get_query_timestamp(&self) -> u64; fn get_ping_time(&self) -> f64; fn get_is_blocked(&self) -> bool; fn get_is_cached(&self) -> bool; fn get_remote_mac(&self) -> [u8; 6]; fn get_remote_addr(&self) -> String; fn get_local_addr(&self) -> String; fn is_prefetch_request(&self) -> bool; fn is_dualstack_request(&self) -> bool; } pub struct DnsRequest_C { request: *mut smartdns_c::dns_request, } #[allow(dead_code)] impl DnsRequest_C { fn new(request: *mut smartdns_c::dns_request) -> DnsRequest_C { unsafe { smartdns_c::dns_server_request_get(request); } DnsRequest_C { request } } fn put_ref(&mut self) { unsafe { smartdns_c::dns_server_request_put(self.request); self.request = std::ptr::null_mut(); } } } #[allow(dead_code)] impl DnsRequest for DnsRequest_C { fn get_group_name(&self) -> String { unsafe { let group_name = smartdns_c::dns_server_request_get_group_name(self.request); std::ffi::CStr::from_ptr(group_name) .to_string_lossy() .into_owned() } } fn get_domain(&self) -> String { unsafe { let domain = smartdns_c::dns_server_request_get_domain(self.request); std::ffi::CStr::from_ptr(domain) .to_string_lossy() .into_owned() } } fn get_qtype(&self) -> u32 { unsafe { smartdns_c::dns_server_request_get_qtype(self.request) as u32 } } fn get_qclass(&self) -> i32 { unsafe { smartdns_c::dns_server_request_get_qclass(self.request) } } fn get_id(&self) -> u16 { unsafe { smartdns_c::dns_server_request_get_id(self.request) as u16 } } fn get_rcode(&self) -> u16 { unsafe { smartdns_c::dns_server_request_get_rcode(self.request) as u16 } } fn get_query_time(&self) -> i32 { unsafe { smartdns_c::dns_server_request_get_query_time(self.request) } } fn get_query_timestamp(&self) -> u64 { unsafe { smartdns_c::dns_server_request_get_query_timestamp(self.request) } } fn get_ping_time(&self) -> f64 { let v = unsafe { smartdns_c::dns_server_request_get_ping_time(self.request) }; let mut ping_time = v as f64; ping_time = (ping_time * 10.0).round() / 10.0; ping_time } fn get_is_blocked(&self) -> bool { unsafe { smartdns_c::dns_server_request_is_blocked(self.request) != 0 } } fn get_is_cached(&self) -> bool { unsafe { smartdns_c::dns_server_request_is_cached(self.request) != 0 } } fn get_remote_mac(&self) -> [u8; 6] { unsafe { let _mac_ptr = smartdns_c::dns_server_request_get_remote_mac(self.request); if _mac_ptr.is_null() { return [0u8; 6]; } let mac = std::slice::from_raw_parts(_mac_ptr, 6); return mac.try_into().unwrap(); } } fn get_remote_addr(&self) -> String { unsafe { let addr = smartdns_c::dns_server_request_get_remote_addr(self.request); if addr.is_null() { return "API".to_string(); } let mut buf = [0u8; 1024]; let retstr = smartdns_c::get_host_by_addr( buf.as_mut_ptr() as *mut c_char, buf.len() as i32, addr as *const smartdns_c::sockaddr, ); if retstr.is_null() { return String::new(); } let addr = std::ffi::CStr::from_ptr(retstr) .to_string_lossy() .into_owned(); addr } } fn get_local_addr(&self) -> String { unsafe { let addr = smartdns_c::dns_server_request_get_local_addr(self.request); let mut buf = [0u8; 1024]; let retstr = smartdns_c::get_host_by_addr( buf.as_mut_ptr() as *mut c_char, buf.len() as i32, addr as *const smartdns_c::sockaddr, ); if retstr.is_null() { return String::new(); } let addr = std::ffi::CStr::from_ptr(retstr) .to_string_lossy() .into_owned(); addr } } fn is_prefetch_request(&self) -> bool { unsafe { smartdns_c::dns_server_request_is_prefetch(self.request) != 0 } } fn is_dualstack_request(&self) -> bool { unsafe { smartdns_c::dns_server_request_is_dualstack(self.request) != 0 } } } impl Drop for DnsRequest_C { fn drop(&mut self) { self.put_ref(); } } impl Clone for DnsRequest_C { fn clone(&self) -> Self { unsafe { smartdns_c::dns_server_request_get(self.request); } DnsRequest_C { request: self.request, } } } unsafe impl Send for DnsRequest_C {} unsafe impl Sync for DnsRequest_C {} pub struct DnsServerStats { stats: *mut smartdns_c::dns_server_stats, server_info: *mut smartdns_c::dns_server_info, } impl DnsServerStats { fn new( stats: *mut smartdns_c::dns_server_stats, server_info: *mut smartdns_c::dns_server_info, ) -> Self { unsafe { smartdns_c::dns_client_server_info_get(server_info) }; DnsServerStats { stats, server_info } } pub fn get_query_total(&self) -> u64 { unsafe { smartdns_c::dns_stats_server_stats_total_get(self.stats) } } pub fn get_query_success(&self) -> u64 { unsafe { smartdns_c::dns_stats_server_stats_success_get(self.stats) } } pub fn get_query_recv(&self) -> u64 { unsafe { smartdns_c::dns_stats_server_stats_recv_get(self.stats) } } pub fn get_success_rate(&self) -> f64 { let total = self.get_query_total(); let success = self.get_query_success(); let mut success_rate: f64 = 0.0; if total == 0 { return success_rate; } success_rate = success as f64 / total as f64 * 100.0; success_rate = (success_rate * 10.0).round() / 10.0; success_rate } pub fn get_query_avg_time(&self) -> f64 { let v = unsafe { smartdns_c::dns_stats_server_stats_avg_time_get(self.stats) }; let mut avg_time = v as f64; avg_time = (avg_time * 10.0).round() / 10.0; avg_time } } impl Drop for DnsServerStats { fn drop(&mut self) { unsafe { if !self.server_info.is_null() { smartdns_c::dns_client_server_info_release(self.server_info); } } } } pub struct DnsUpstreamServer { server_info: *mut smartdns_c::dns_server_info, } impl DnsUpstreamServer { fn new(server_info: *mut smartdns_c::dns_server_info) -> Self { unsafe { smartdns_c::dns_client_server_info_get(server_info); } DnsUpstreamServer { server_info } } pub fn get_server_num() -> i32 { unsafe { smartdns_c::dns_server_num() } } pub fn dns_server_alive_num() -> i32 { unsafe { smartdns_c::dns_server_alive_num() } } pub fn get_server_stats(&self) -> DnsServerStats { let stats = unsafe { smartdns_c::dns_client_get_server_stats(self.server_info) }; DnsServerStats::new(stats, self.server_info) } pub fn is_server_alive(&self) -> bool { unsafe { smartdns_c::dns_client_server_is_alive(self.server_info) != 0 } } pub fn get_server_list() -> Result, String> { let mut servers = Vec::new(); let server_num = DnsUpstreamServer::get_server_num(); unsafe { let mut server_info: Vec<*mut smartdns_c::dns_server_info> = Vec::with_capacity(server_num as usize); let ret = smartdns_c::dns_client_get_server_info_lists(server_info.as_mut_ptr(), server_num); if ret < 0 { return Err(("get server info failed.").to_string()); } if ret > server_num { return Err(("get server info failed.").to_string()); } server_info.set_len(ret as usize); for i in 0..ret { let server_info = server_info[i as usize]; servers.push(DnsUpstreamServer::new(server_info)); smartdns_c::dns_client_server_info_release(server_info); } } Ok(servers) } pub fn get_ip(&self) -> String { unsafe { let ip = smartdns_c::dns_client_get_server_ip(self.server_info); std::ffi::CStr::from_ptr(ip).to_string_lossy().into_owned() } } pub fn get_host(&self) -> String { unsafe { let host = smartdns_c::dns_client_get_server_host(self.server_info); std::ffi::CStr::from_ptr(host) .to_string_lossy() .into_owned() } } pub fn get_port(&self) -> u16 { unsafe { smartdns_c::dns_client_get_server_port(self.server_info) as u16 } } pub fn get_type(&self) -> DnsServerType { unsafe { let t = smartdns_c::dns_client_get_server_type(self.server_info) as smartdns_c::dns_server_type_t; DnsServerType::from(t) } } pub fn get_server_security_status(&self) -> DnsServerTrustStatus { unsafe { let t = smartdns_c::dns_client_get_server_security_status(self.server_info) as smartdns_c::dns_server_security_status; DnsServerTrustStatus::from(t) } } pub fn get_groups(&self) -> Vec { let groups = Vec::new(); groups } } impl Drop for DnsUpstreamServer { fn drop(&mut self) { unsafe { smartdns_c::dns_client_server_info_release(self.server_info); } } } impl Clone for DnsUpstreamServer { fn clone(&self) -> Self { unsafe { smartdns_c::dns_client_server_info_get(self.server_info); } DnsUpstreamServer { server_info: self.server_info, } } } unsafe impl Send for DnsUpstreamServer {} pub trait SmartdnsOperations { fn server_query_complete(&self, request: Box); fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32); fn server_audit_log(&self, msg: &str, msg_len: i32); fn server_init(&mut self, args: &Vec) -> Result<(), Box>; fn server_exit(&mut self); } pub static mut PLUGIN: Plugin = Plugin { args: Vec::new(), ops: None, }; pub struct Plugin { args: Vec, ops: Option>, } pub struct SmartdnsCert { pub key: String, pub cert: String, pub password: String, } #[allow(dead_code)] impl Plugin { pub fn get_args(&self) -> &Vec { &self.args } pub fn set_operation(&mut self, ops: Box) { self.ops = Some(ops); } pub fn clear_operation(&mut self) { self.ops = None; } pub fn smartdns_exit(status: i32) { unsafe { smartdns_c::smartdns_exit(status); } } pub fn smartdns_restart() { unsafe { smartdns_c::smartdns_restart(); } } pub fn smartdns_get_cert() -> Result { unsafe { let mut key = [0u8; 4096]; let mut cert = [0u8; 4096]; let ret = smartdns_c::smartdns_get_cert( key.as_mut_ptr() as *mut c_char, cert.as_mut_ptr() as *mut c_char, ); if ret != 0 { return Err("get cert error".to_string()); } let key = std::ffi::CStr::from_ptr(key.as_ptr() as *const c_char) .to_string_lossy() .into_owned(); let cert = std::ffi::CStr::from_ptr(cert.as_ptr() as *const c_char) .to_string_lossy() .into_owned(); Ok(SmartdnsCert { key, cert, password: "".to_string(), }) } } pub fn dns_cache_flush() { unsafe { smartdns_c::dns_cache_flush(); } } pub fn dns_cache_total_num() -> i32 { unsafe { smartdns_c::dns_cache_total_num() } } #[allow(dead_code)] pub fn dns_conf_cache_dir() -> String { unsafe { let cache_dir = smartdns_c::dns_conf_get_cache_dir(); std::ffi::CStr::from_ptr(cache_dir) .to_string_lossy() .into_owned() } } #[allow(dead_code)] pub fn dns_conf_data_dir() -> String { unsafe { let data_dir = smartdns_c::dns_conf_get_data_dir(); std::ffi::CStr::from_ptr(data_dir) .to_string_lossy() .into_owned() } } #[allow(dead_code)] pub fn dns_conf_plugin_config(key: &str) -> Option { let key = CString::new(key).expect("Failed to convert to CString"); unsafe { let value = smartdns_c::smartdns_plugin_get_config(key.as_ptr()); if value.is_null() { return None; } Some( std::ffi::CStr::from_ptr(value) .to_string_lossy() .into_owned(), ) } } #[allow(dead_code)] pub fn dns_conf_plugin_config_default(key: &str, default_val: &String) -> String { let v = Plugin::dns_conf_plugin_config(key); if let Some(v) = v { return v; } default_val.clone() } #[allow(dead_code)] pub fn dns_conf_plugin_clear_all_config() { unsafe { smartdns_c::smartdns_plugin_clear_all_config(); } } fn parser_args(&mut self, plugin: *mut smartdns_c::dns_plugin) -> Result<(), String> { let argc = unsafe { smartdns_c::dns_plugin_get_argc(plugin) }; let args: Vec = unsafe { let argv = smartdns_c::dns_plugin_get_argv(plugin); let mut args = Vec::new(); for i in 0..argc { let arg = std::ffi::CStr::from_ptr(*argv.offset(i as isize)) .to_string_lossy() .into_owned(); args.push(arg); } args }; self.args = args; Ok(()) } } pub struct Stats {} impl Stats { pub fn get_avg_process_time() -> f64 { unsafe { let v = smartdns_c::dns_stats_avg_time_get(); let mut process_time = v as f64; process_time = (process_time * 10.0).round() / 10.0; process_time } } pub fn get_request_total() -> u64 { unsafe { smartdns_c::dns_stats_request_total_get() } } pub fn get_request_success() -> u64 { unsafe { smartdns_c::dns_stats_request_success_get() } } pub fn get_request_from_client() -> u64 { unsafe { smartdns_c::dns_stats_request_from_client_get() } } pub fn get_request_blocked() -> u64 { unsafe { smartdns_c::dns_stats_request_blocked_get() } } pub fn get_cache_hit() -> u64 { unsafe { smartdns_c::dns_stats_cache_hit_get() } } pub fn get_cache_memsize() -> u64 { unsafe { smartdns_c::dns_cache_total_memsize() as u64 } } pub fn get_cache_hit_rate() -> f64 { unsafe { let v = smartdns_c::dns_stats_cache_hit_rate_get() as f64; let mut cache_hit_rate = v as f64; cache_hit_rate = (cache_hit_rate * 10.0).round() / 10.0; cache_hit_rate } } pub fn get_cache_memory_size() -> u64 { unsafe { smartdns_c::dns_cache_total_memsize() as u64 } } } impl fmt::Display for Stats { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Stats") } } #[cfg(test)] mod tests { use super::*; #[test] fn test_dns_log() { dns_log!(LogLevel::DEBUG, "test log"); } } ================================================ FILE: plugin/smartdns-ui/src/utils.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 nix::libc; use pbkdf2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Pbkdf2, }; pub fn parse_value(value: Option, min: T, max: T, default: T) -> T where T: PartialOrd + std::str::FromStr, { if value.is_none() { return default; } let value = value.unwrap().parse::(); if let Err(_) = value { return default; } let mut value = value.unwrap_or_else(|_| default); if value < min { value = min; } if value > max { value = max; } value } pub fn seconds_until_next_hour() -> u64 { let now = chrono::Local::now(); let minutes = chrono::Timelike::minute(&now); let seconds = chrono::Timelike::second(&now); let remaining_seconds = 3600 - (minutes * 60 + seconds) as u64; remaining_seconds } pub fn get_free_disk_space(path: &str) -> u64 { let path = std::ffi::CString::new(path).unwrap(); let mut statvfs: libc::statvfs = unsafe { std::mem::zeroed() }; let ret = unsafe { libc::statvfs(path.as_ptr(), &mut statvfs) }; if ret != 0 { return 0; } statvfs.f_bsize as u64 * statvfs.f_bavail as u64 } pub fn hash_password(password: &str, round: Option) -> Result> { let salt = SaltString::generate(&mut OsRng); let mut parm = pbkdf2::Params::default(); parm.rounds = round.unwrap_or(10000); let password_hash = Pbkdf2 .hash_password_customized(password.as_bytes(), None, None, parm, &salt) .map_err(|e| e.to_string())? .to_string(); Ok(password_hash) } pub fn verify_password(password: &str, password_hash: &str) -> bool { let parsed_hash = match PasswordHash::new(&password_hash) { Ok(h) => h, Err(_) => return false, }; Pbkdf2 .verify_password(password.as_bytes(), &parsed_hash) .is_ok() } pub fn get_page_size() -> usize { // Use libc to get the system page size unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } } pub fn is_dir_writable(path: &str) -> bool { let path = std::ffi::CString::new(path).unwrap(); unsafe { libc::access(path.as_ptr(), libc::W_OK) == 0 } } pub fn is_ipv6_supported() -> bool { let sock = unsafe { libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0) }; if sock < 0 { return false; } unsafe { libc::close(sock) }; true } ================================================ FILE: plugin/smartdns-ui/src/whois.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 std::collections::HashMap; use std::error::Error; use std::sync::Mutex; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use crate::dns_log; use crate::smartdns::LogLevel; #[derive(Debug, Clone)] pub struct WhoIsInfo { pub domain: String, pub organization: String, pub address: String, pub city: String, pub country: String, pub registrar: String, pub refer: String, } pub struct WhoIs { root_server: String, server_lists: Mutex>, } impl WhoIs { pub fn new() -> Self { WhoIs { root_server: "whois.iana.org".to_string(), server_lists: Mutex::new(HashMap::new()), } } pub fn parser_whois(&self, whois: &str, whois_domain: &mut String) -> WhoIsInfo { let mut info = WhoIsInfo { domain: String::new(), registrar: String::new(), organization: String::new(), address: String::new(), city: String::new(), country: String::new(), refer: String::new(), }; for line in whois.lines() { let line = line.trim(); if let Some(value) = line.strip_prefix("Organization:") { info.organization = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("Registrar:") { info.registrar = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("Registrant:") { info.organization = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("Registrant Name:") { info.organization = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("Registrant Organization:") { info.organization = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("Registrant Street:") { info.address = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("Registrant City:") { info.city = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("Registrant Country:") { info.country = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("Registrant Country Code:") { info.country = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("Registrar WHOIS Server:") { info.refer = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("refer:") { info.refer = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("whois:") { info.refer = value.trim().to_string(); } else if let Some(value) = line.strip_prefix("domain:") { *whois_domain = value.trim().to_string(); } } info } pub async fn query_from_server(domain: &str, server: &str) -> Result> { let stream = TcpStream::connect(server); let stream_timeout = tokio::time::timeout(tokio::time::Duration::from_secs(20), stream).await; if let Err(_) = stream_timeout { return Err("Connect whois server timeout.".into()); } let mut stream = stream_timeout.unwrap()?; let query = format!("{}\r\n", domain); stream.write_all(query.as_bytes()).await?; let mut output = String::new(); let read_timeout = tokio::time::timeout( tokio::time::Duration::from_secs(10), stream.read_to_string(&mut output), ) .await; if let Err(_) = read_timeout { return Err("Read whois server timeout.".into()); } stream.shutdown().await?; Ok(output) } pub fn add_server(&self, domain: &str, server: &str) { let mut server_lists = self.server_lists.lock().unwrap(); server_lists.insert(domain.to_string().to_lowercase(), server.to_string()); } pub fn get_server(&self, domain: &str) -> Option { let server_lists = self.server_lists.lock().unwrap(); if let Some(server) = server_lists.get(domain) { return Some(server.clone()); } None } pub fn get_server_by_domain(&self, domain: &str) -> String { let mut domain_tmp = domain.to_string().to_lowercase(); loop { let server = self.get_server(domain_tmp.as_str()); if let Some(s) = server { return s; } if let Some(index) = domain_tmp.find('.') { domain_tmp = domain_tmp[index + 1..].to_string(); } else { return self.root_server.clone(); } } } pub async fn query(&self, domain: &str) -> Result> { let parts: Vec<&str> = domain.split('.').collect(); let mut domainlist = Vec::new(); if parts.len() < 2 { domainlist.push(domain.to_string()); } else if parts.len() < 3 { let v = format!("{}.{}", parts[parts.len() - 2], parts[parts.len() - 1]); domainlist.push(v); } else { let v = format!("{}.{}", parts[parts.len() - 2], parts[parts.len() - 1]); domainlist.push(v); let v = format!( "{}.{}.{}", parts[parts.len() - 3], parts[parts.len() - 2], parts[parts.len() - 1] ); domainlist.push(v); }; let mut last_whoisinfo = WhoIsInfo { domain: domain.to_string(), organization: String::new(), address: String::new(), city: String::new(), country: String::new(), registrar: String::new(), refer: String::new(), }; for main_domain in domainlist { let mut base_domain = main_domain.as_str(); if let Some(v) = main_domain.find(".") { if v > 0 { base_domain = main_domain.split_at(v + 1).1; } } let mut server = self.get_server_by_domain(base_domain); loop { let mut whois_domain = String::new(); if server.is_empty() { break; } let connect_host = format!("{}:43", server); dns_log!( LogLevel::DEBUG, "Query whois server: {}, domain:{}", connect_host, main_domain ); let whois = WhoIs::query_from_server(main_domain.as_str(), &connect_host).await; if let Err(e) = whois { if last_whoisinfo.registrar.len() > 0 { return Ok(last_whoisinfo); } return Err(e); } let whois = whois.unwrap(); let mut info = self.parser_whois(&whois, &mut whois_domain); info.domain = domain.to_string(); if info.organization.len() > 0 { return Ok(info); } if info.registrar.len() > 0 { last_whoisinfo = info.clone(); } if server == info.refer { break; } server = info.refer; if whois_domain.len() > 0 { self.add_server(whois_domain.as_str(), server.as_str()); } } } if last_whoisinfo.registrar.len() > 0 { return Ok(last_whoisinfo); } Err("Cannot find whois.".into()) } } ================================================ FILE: plugin/smartdns-ui/tests/common/client.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 http::uri; use reqwest; use smartdns_ui::http_api_msg; use std::{error::Error, net::TcpStream}; use tungstenite::*; pub struct TestClient { url: String, token: Option, client: reqwest::blocking::Client, no_auth_header: bool, } impl TestClient { pub fn new(url: &String) -> Self { let client = TestClient { url: url.clone(), token: None, client: reqwest::blocking::ClientBuilder::new() .danger_accept_invalid_certs(true) .build() .unwrap(), no_auth_header: false, }; client } #[allow(dead_code)] pub fn set_with_auth_header(&mut self, with_auth_header: bool) { self.no_auth_header = with_auth_header; } #[allow(dead_code)] pub fn login(&mut self, username: &str, password: &str) -> Result> { let url = self.url.clone() + "/api/auth/login"; let body = http_api_msg::api_msg_gen_auth_login(&http_api_msg::AuthUser { username: username.to_string(), password: password.to_string(), }); let resp = self.client.post(&url).body(body).send()?; if resp.status().as_u16() != 200 { return Err(resp.text()?.into()); } let text = resp.text()?; let token = http_api_msg::api_msg_parse_auth_token(&text)?; self.token = Some(token); Ok(text) } #[allow(dead_code)] pub fn logout(&mut self) -> Result> { let url = self.url.clone() + "/api/auth/logout"; let resp = self.client.post(&url).send()?; let text = resp.text()?; self.token = None; Ok(text) } fn prep_request( &self, method: reqwest::Method, path: &str, ) -> Result> { let url = self.url.clone() + path; let mut req = self.client.request(method, url); if let Some(token) = &self.token { if self.token.is_some() && !self.no_auth_header { req = req.header("Authorization", format!("Bearer {}", token.token)); } } Ok(req) } pub fn get(&self, path: &str) -> Result<(i32, String), Box> { let req = self.prep_request(reqwest::Method::GET, path)?; let resp = req.send()?; let status = resp.status().as_u16(); let text = resp.text()?; Ok((status as i32, text)) } #[allow(dead_code)] pub fn delete(&self, path: &str) -> Result<(i32, String), Box> { let req = self.prep_request(reqwest::Method::DELETE, path)?; let resp = req.send()?; let status = resp.status().as_u16(); let text = resp.text()?; Ok((status as i32, text)) } #[allow(dead_code)] pub fn put(&self, path: &str, body: &str) -> Result<(i32, String), Box> { let req = self.prep_request(reqwest::Method::PUT, path)?; let resp = req.body(body.to_string()).send()?; let status = resp.status().as_u16(); let text = resp.text()?; Ok((status as i32, text)) } #[allow(dead_code)] pub fn post(&self, path: &str, body: &str) -> Result<(i32, String), Box> { let req = self.prep_request(reqwest::Method::POST, path)?; let resp = req.body(body.to_string()).send()?; let status = resp.status().as_u16(); let text = resp.text()?; Ok((status as i32, text)) } #[allow(dead_code)] pub fn websocket( &self, path: &str, ) -> Result>, Box> { let url = self.url.clone() + path; let uri: http::Uri = url.parse()?; let mut parts = uri.into_parts(); parts.scheme = Some("ws".parse().unwrap()); let uri = uri::Uri::from_parts(parts).unwrap(); let mut request_builder = tungstenite::ClientRequestBuilder::new(uri); if let Some(token) = &self.token { if self.token.is_some() { request_builder = request_builder.with_header("Authorization", format!("Bearer {}", token.token)); } } request_builder = request_builder .with_header("Upgrade", "websocket") .with_header("Sec-WebSocket-Version", "13") .with_header("Connection", "keep-alive, Upgrade"); let ret = tungstenite::connect(request_builder); if let Err(e) = ret { println!("websocket connect error: {:?}", e.to_string()); return Err(Box::new(e)); } let (socket, _) = ret.unwrap(); Ok(socket) } } impl Drop for TestClient { fn drop(&mut self) {} } ================================================ FILE: plugin/smartdns-ui/tests/common/mod.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 . */ mod server; mod client; pub use server::*; pub use client::*; ================================================ FILE: plugin/smartdns-ui/tests/common/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 smartdns_ui::data_server::DataServer; use smartdns_ui::db::*; use smartdns_ui::dns_log; use smartdns_ui::plugin::*; use smartdns_ui::smartdns::*; use std::io::Write; use std::sync::Arc; use tempfile::TempDir; static INSTANCE_LOCK: std::sync::RwLock<()> = std::sync::RwLock::new(()); pub struct InstanceLockGuard<'a> { _read_guard: Option>, _write_guard: Option>, } impl<'a> InstanceLockGuard<'a> { pub fn new_read_guard() -> Self { Self { _read_guard: Some(INSTANCE_LOCK.read().unwrap()), _write_guard: None, } } pub fn new_write_guard() -> Self { Self { _read_guard: None, _write_guard: Some(INSTANCE_LOCK.write().unwrap()), } } } pub struct TestDnsRequest { pub domain: String, pub group_name: String, pub qtype: u32, pub qclass: i32, pub id: u16, pub rcode: u16, pub query_time: i32, pub query_timestamp: u64, pub ping_time: f64, pub is_blocked: bool, pub is_cached: bool, pub remote_mac: [u8; 6], pub remote_addr: String, pub local_addr: String, pub prefetch_request: bool, pub dualstack_request: bool, pub drop_callback: Option>, } #[allow(dead_code)] impl TestDnsRequest { pub fn new() -> Self { TestDnsRequest { domain: "".to_string(), group_name: "default".to_string(), qtype: 1, qclass: 1, id: 0, rcode: 2, query_time: 0, query_timestamp: get_utc_time_ms(), ping_time: -0.1 as f64, is_blocked: false, is_cached: false, remote_mac: [0; 6], remote_addr: "127.0.0.1".to_string(), local_addr: "127.0.0.1".to_string(), prefetch_request: false, dualstack_request: false, drop_callback: None, } } } #[allow(dead_code)] impl DnsRequest for TestDnsRequest { fn get_group_name(&self) -> String { self.group_name.clone() } fn get_domain(&self) -> String { self.domain.clone() } fn get_qtype(&self) -> u32 { self.qtype } fn get_qclass(&self) -> i32 { self.qclass } fn get_id(&self) -> u16 { self.id } fn get_rcode(&self) -> u16 { self.rcode } fn get_query_time(&self) -> i32 { self.query_time } fn get_query_timestamp(&self) -> u64 { self.query_timestamp } fn get_ping_time(&self) -> f64 { self.ping_time } fn get_is_blocked(&self) -> bool { self.is_blocked } fn get_is_cached(&self) -> bool { self.is_cached } fn get_remote_mac(&self) -> [u8; 6] { self.remote_mac } fn get_remote_addr(&self) -> String { self.remote_addr.clone() } fn get_local_addr(&self) -> String { self.local_addr.clone() } fn is_prefetch_request(&self) -> bool { self.prefetch_request } fn is_dualstack_request(&self) -> bool { self.dualstack_request } } impl Drop for TestDnsRequest { fn drop(&mut self) { if let Some(f) = &self.drop_callback { f(); } } } unsafe impl Send for TestDnsRequest {} unsafe impl Sync for TestDnsRequest {} #[allow(dead_code)] struct TestSmartDnsConfigItem { pub key: String, pub value: String, } pub struct TestSmartDnsServer { confs: Vec, is_started: bool, workdir: String, thread: Option>, } impl TestSmartDnsServer { pub fn new() -> Self { let mut server = TestSmartDnsServer { confs: Vec::new(), is_started: false, workdir: "/tmp/smartdns-test.conf".to_string(), thread: None, }; server.add_conf("bind", ":66603"); server.add_conf("log-level", "debug"); server.add_conf("log-num", "0"); server.add_conf("cache-persist", "no"); server } pub fn set_workdir(&mut self, workdir: &str) { self.workdir = workdir.to_string(); } pub fn add_conf(&mut self, key: &str, value: &str) { self.confs.push(TestSmartDnsConfigItem { key: key.to_string(), value: value.to_string(), }); } fn gen_conf_file(&self) -> std::io::Result { let file = self.workdir.clone() + "/smartdns.conf"; let mut f = std::fs::File::create(&file)?; for conf in self.confs.iter() { f.write_all(format!("{} {}\n", conf.key, conf.value).as_bytes())?; } Ok(file) } pub fn start(&mut self) -> Result<(), Box> { let conf_file = self.gen_conf_file()?; let t = std::thread::spawn(move || { dns_log!(LogLevel::ERROR, "smartdns server run start..."); smartdns_ui::smartdns::smartdns_server_run(&conf_file).unwrap(); dns_log!(LogLevel::ERROR, "smartdns server run exit..."); }); self.thread = Some(t); self.is_started = true; dns_log!(LogLevel::ERROR, "smartdns_server_run"); Ok(()) } pub fn stop(&mut self) { if !self.is_started { return; } self.is_started = false; smartdns_ui::smartdns::smartdns_server_stop(); if self.thread.is_none() { return; } let _ = self.thread.take().unwrap().join(); } } impl Drop for TestSmartDnsServer { fn drop(&mut self) { self.stop(); } } pub struct TestServer { dns_server: TestSmartDnsServer, dns_server_enable: bool, plugin: Arc, args: Vec, workdir: String, temp_dir: TempDir, www_root: String, is_started: bool, ip: String, is_https: bool, log_level: LogLevel, old_log_level: LogLevel, one_instance: bool, instance_lock_guard: Option>, } impl TestServer { pub fn new() -> Self { let mut server = TestServer { dns_server: TestSmartDnsServer::new(), dns_server_enable: false, plugin: SmartdnsPlugin::new(), args: Vec::new(), workdir: String::new(), temp_dir: TempDir::with_prefix("smartdns-ui-").unwrap(), www_root: String::new(), is_started: false, ip: "http://127.0.0.1:0".to_string(), is_https: false, log_level: LogLevel::INFO, old_log_level: LogLevel::INFO, one_instance: false, instance_lock_guard: None, }; server.workdir = server.temp_dir.path().to_str().unwrap().to_string(); server.dns_server.set_workdir(&server.workdir); server.get_data_server().set_recv_in_batch(false); server } fn setup_default_args(&mut self) { self.args.insert(0, "--ip".to_string()); self.args.insert(1, self.ip.clone()); self.args.insert(0, "--data-dir".to_string()); self.args.insert(1, self.workdir.clone()); self.args.insert(0, "--www-root".to_string()); self.www_root = self.workdir.clone() + "/www"; self.args.insert(1, self.www_root.clone()); self.args.insert(0, "smartdns-ui".to_string()); dns_log!(LogLevel::INFO, "workdir: {}", self.workdir); } #[allow(dead_code)] pub fn get_url(&self, path: &str) -> String { self.ip.clone() + path } pub fn get_host(&self) -> String { self.ip.clone() } #[allow(dead_code)] pub fn get_www_root(&self) -> &String { &self.www_root } fn create_workdir(&self) -> std::io::Result<()> { std::fs::create_dir_all(&self.workdir)?; std::fs::create_dir_all(&self.www_root)?; Ok(()) } fn remove_workdir(&self) -> std::io::Result<()> { let r = std::fs::remove_dir_all(&self.workdir); return r; } #[allow(dead_code)] pub fn add_mock_server_conf(&mut self, key: &str, value: &str) { self.dns_server.add_conf(key, value); } #[allow(dead_code)] pub fn enable_mock_server(&mut self) { self.dns_server_enable = true; self.set_one_instance(true); } #[allow(dead_code)] pub fn add_args(&mut self, args: Vec) { for arg in args.iter() { self.args.push(arg.clone()); } } #[allow(dead_code)] pub fn send_test_dnsrequest( &mut self, mut request: TestDnsRequest, ) -> Result<(), Box> { let batch_mode = self.get_data_server().get_recv_in_batch(); let (tx, rx) = std::sync::mpsc::channel(); let request_drop_callback = move || { tx.send(()).unwrap(); }; if batch_mode == false { request.drop_callback = Some(Box::new(request_drop_callback)); } let ret = self.plugin.query_complete(Box::new(request)); if let Err(e) = ret { dns_log!(LogLevel::ERROR, "send_test_dnsrequest error: {:?}", e); return Err(e); } if batch_mode == false { rx.recv().unwrap(); } Ok(()) } #[allow(dead_code)] pub fn new_mock_domain_record(&self) -> DomainData { DomainData { id: 0, timestamp: smartdns_ui::smartdns::get_utc_time_ms(), domain: "example.com".to_string(), domain_type: 1, client: "127.0.0.1".to_string(), domain_group: "default".to_string(), reply_code: 0, query_time: 0, ping_time: -0.1 as f64, is_blocked: false, is_cached: false, } } #[allow(dead_code)] pub fn get_data_server(&self) -> Arc { self.plugin.get_data_server() } #[allow(dead_code)] pub fn add_domain_record( &mut self, record: &DomainData, ) -> Result<(), Box> { self.plugin.get_data_server().insert_domain(record) } pub fn set_log_level(&mut self, level: LogLevel) { self.log_level = level; } fn init_server(&mut self) -> Result<(), Box> { self.create_workdir()?; self.old_log_level = smartdns_ui::smartdns::dns_log_get_level(); smartdns_ui::smartdns::dns_log_set_level(self.log_level); Ok(()) } #[allow(dead_code)] pub fn set_https(&mut self, enable: bool) { self.is_https = enable; if enable { self.ip = "https://127.0.0.1:0".to_string(); } else { self.ip = "http://127.0.0.1:0".to_string(); } } pub fn set_one_instance(&mut self, one_instance: bool) { self.one_instance = one_instance; } pub fn start(&mut self) -> Result<(), Box> { if self.one_instance { self.instance_lock_guard = Some(InstanceLockGuard::new_write_guard()); if self.dns_server_enable { let ret = self.dns_server.start(); if let Err(e) = ret { dns_log!(LogLevel::ERROR, "start dns server failed: {:?}", e); return Err(e); } } } else { self.instance_lock_guard = Some(InstanceLockGuard::new_read_guard()); } self.setup_default_args(); dns_log!(LogLevel::INFO, "TestServer start"); let ret = self.init_server(); if let Err(e) = ret { dns_log!(LogLevel::ERROR, "init server failed: {:?}", e); return Err(e); } let result = self.plugin.start(&self.args); if let Err(e) = result { dns_log!(LogLevel::ERROR, "start error: {:?}", e); return Err(e); } let addr = self.plugin.get_http_server().get_local_addr(); if addr.is_none() { return Err(Box::new(std::io::Error::new( std::io::ErrorKind::Other, "get local addr failed", ))); } let addr = addr.unwrap(); if self.is_https { self.ip = format!("https://{}:{}", addr.ip(), addr.port()); } else { self.ip = format!("http://{}:{}", addr.ip(), addr.port()); } self.is_started = true; Ok(()) } pub fn stop(&mut self) { if !self.is_started { return; } dns_log!(LogLevel::INFO, "TestServer stop"); self.plugin.stop(); self.is_started = false; self.one_instance = false; smartdns_ui::smartdns::dns_log_set_level(self.old_log_level); self.dns_server.stop(); self.instance_lock_guard = None; } } impl Drop for TestServer { fn drop(&mut self) { self.stop(); let _ = self.remove_workdir(); } } ================================================ FILE: plugin/smartdns-ui/tests/httpserver_test.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 . */ mod common; use smartdns_ui::smartdns::LogLevel; use std::{fs::File, io::Write}; #[test] fn test_http_server_indexhtml() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let www_root = server.get_www_root(); let mut index_html_file = File::create(www_root.clone() + "/index.html").unwrap(); let content = "Hello, world!"; index_html_file.write_all(content.as_bytes()).unwrap(); let client = common::TestClient::new(&server.get_host()); let c = client.get("/"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); assert_eq!(body, content); } #[test] fn test_http_server_somehtml() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let www_root = server.get_www_root(); let mut index_html_file = File::create(www_root.clone() + "/index.html").unwrap(); let content = "Hello, world!"; index_html_file.write_all(content.as_bytes()).unwrap(); let mut some_html_file = File::create(www_root.clone() + "/some.html").unwrap(); let some_content = "Some index file!"; some_html_file.write_all(some_content.as_bytes()).unwrap(); let client = common::TestClient::new(&server.get_host()); let c = client.get("/some.html"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); assert_eq!(body, some_content); } #[test] fn test_http_server_redirect_indexhtml() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let www_root = server.get_www_root(); let mut index_html_file = File::create(www_root.clone() + "/index.html").unwrap(); let content = "Hello, world!"; index_html_file.write_all(content.as_bytes()).unwrap(); let client = common::TestClient::new(&server.get_host()); let c = client.get("/some.html"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); assert_eq!(body, content); } #[test] fn test_http_server_404() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let client = common::TestClient::new(&server.get_host()); let c = client.get("/index.html"); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 404); } ================================================ FILE: plugin/smartdns-ui/tests/restapi_test.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 . */ mod common; use common::TestDnsRequest; use nix::libc::c_char; use smartdns_ui::{http_api_msg, http_jwt::JwtClaims, smartdns::LogLevel}; use std::ffi::CString; #[test] fn test_rest_api_login() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let body = res.unwrap(); println!("res: {}", body); let result = http_api_msg::api_msg_parse_auth_token(&body); assert!(result.is_ok()); let token = result.unwrap(); assert!(!token.token.is_empty()); let calims = jsonwebtoken::dangerous::insecure_decode::(&token.token); println!("calims: {:?}", calims); assert_eq!(token.expires_in, "600"); assert!(calims.is_ok()); let calims = calims.unwrap(); let calims = calims.claims; assert_eq!(calims.user, "admin"); } #[test] fn test_rest_api_logout() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); client.set_with_auth_header(false); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/cache/count"); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 200); let ret = client.logout(); assert!(ret.is_ok()); let c = client.get("/api/cache/count"); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 401); } #[test] fn test_rest_api_login_incorrect() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "wrongpassword"); assert!(!res.is_ok()); let body = res.err().unwrap().to_string(); println!("res: {}", body); let result = http_api_msg::api_msg_parse_error(&body); assert!(result.is_ok()); assert_eq!(result.unwrap(), "Incorrect username or password."); } #[test] fn test_rest_api_change_password() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let password_msg = http_api_msg::api_msg_gen_auth_password_change("wrong_oldpassword", "newpassword"); let c = client.put("/api/auth/password", password_msg.as_str()); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 403); let password_msg = http_api_msg::api_msg_gen_auth_password_change("password", "newpassword"); let c = client.put("/api/auth/password", password_msg.as_str()); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 204); let res = client.login("admin", "password"); assert!(!res.is_ok()); } #[test] fn test_rest_api_cache_count() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/cache/count"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let count = http_api_msg::api_msg_parse_cache_number(&body); assert!(count.is_ok()); assert_eq!(count.unwrap(), 0); } #[test] fn test_rest_api_auth_refresh() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.post("/api/auth/refresh", ""); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let token = http_api_msg::api_msg_parse_auth_token(&body); assert!(token.is_ok()); let token = token.unwrap(); assert!(!token.token.is_empty()); assert_eq!(token.expires_in, "600"); println!("token: {:?}", token); } #[test] fn test_rest_api_auth_check() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let c = client.get("/api/auth/check"); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 401); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/auth/check"); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 200); } #[test] fn test_rest_api_no_permission() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let client = common::TestClient::new(&server.get_host()); let c = client.get("/api/cache/count"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 401); println!("body: {}", body); let error_msg = http_api_msg::api_msg_parse_error(&body); assert!(error_msg.is_ok()); assert_eq!(error_msg.unwrap(), "Please login."); } #[test] fn test_rest_api_404() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.post("/api/404", ""); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 404); let error_msg = http_api_msg::api_msg_parse_error(&body); assert!(error_msg.is_ok()); assert_eq!(error_msg.unwrap(), "API not found."); } #[test] fn test_rest_api_log_stream() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let socket = client.websocket("/api/log/stream"); assert!(socket.is_ok()); let mut socket = socket.unwrap(); _ = socket.send(tungstenite::Message::Text("aaaa".to_string())); _ = socket.close(None); } #[test] fn test_rest_api_log_level() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); server.set_one_instance(true); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/log/level"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let log_level = http_api_msg::api_msg_parse_loglevel(&body); assert!(log_level.is_ok()); assert_eq!(log_level.unwrap(), LogLevel::DEBUG); let level_msg = http_api_msg::api_msg_gen_loglevel(LogLevel::ERROR); let c = client.put("/api/log/level", level_msg.as_str()); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 204); println!("body: {}", body); assert_eq!(smartdns_ui::smartdns::dns_log_get_level(), LogLevel::ERROR); } #[test] fn test_rest_api_get_domain() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); for i in 0..1024 { let mut request = TestDnsRequest::new(); request.domain = format!("{}.com", i); request.id = i as u16; assert!(server.send_test_dnsrequest(request).is_ok()); } let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/domain/count"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let count = http_api_msg::api_msg_parse_count(&body); assert!(count.is_ok()); assert_eq!(count.unwrap(), 1024); let c = client.get("/api/domain?page_num=11&page_size=10&order=asc"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let result = http_api_msg::api_msg_parse_domain_list(&body); assert!(result.is_ok()); let result = result.unwrap(); assert_eq!(result.len(), 10); assert_eq!(result[0].id, 101); assert_eq!(result[0].domain, "100.com"); } #[test] fn test_rest_api_audit_log_stream() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let socket = client.websocket("/api/log/audit/stream"); assert!(socket.is_ok()); let mut socket = socket.unwrap(); _ = socket.send(tungstenite::Message::Text("aaaa".to_string())); _ = socket.close(None); } #[test] fn test_rest_api_get_by_id() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); for i in 0..1024 { let mut request = TestDnsRequest::new(); request.domain = format!("{}.com", i); request.id = i as u16; assert!(server.send_test_dnsrequest(request).is_ok()); } let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/domain/1000"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let result = http_api_msg::api_msg_parse_domain(&body); assert!(result.is_ok()); let result = result.unwrap(); assert_eq!(result.id, 1000); assert_eq!(result.domain, "999.com"); } #[test] fn test_rest_api_delete_domain_by_id() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); for i in 0..1024 { let mut request = TestDnsRequest::new(); request.domain = format!("{}.com", i); request.id = i as u16; assert!(server.send_test_dnsrequest(request).is_ok()); } let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.delete("/api/domain/1000"); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 204); let c = client.get("/api/domain/1000"); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 404); let c = client.get("/api/domain/count"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let count = http_api_msg::api_msg_parse_count(&body); assert!(count.is_ok()); assert_eq!(count.unwrap(), 1023); } #[test] fn test_rest_api_server_version() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let client = common::TestClient::new(&server.get_host()); let c = client.get("/api/server/version"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let version = http_api_msg::api_msg_parse_version(&body); assert!(version.is_ok()); let version = version.unwrap(); assert_eq!(version.0, smartdns_ui::smartdns::smartdns_version()); if env!("GIT_VERSION").is_empty() { assert_eq!(version.1, env!("CARGO_PKG_VERSION")); return; } let check_version = std::format!("{} ({})", env!("CARGO_PKG_VERSION"), env!("GIT_VERSION")); assert_eq!(version.1, check_version); } #[test] fn test_rest_api_https_server() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); server.enable_mock_server(); server.set_https(true); assert!(server.start().is_ok()); let client = common::TestClient::new(&server.get_host()); let c = client.get("/api/server/version"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let version = http_api_msg::api_msg_parse_version(&body); assert!(version.is_ok()); let version = version.unwrap(); assert_eq!(version.0, smartdns_ui::smartdns::smartdns_version()); if env!("GIT_VERSION").is_empty() { assert_eq!(version.1, env!("CARGO_PKG_VERSION")); return; } let check_version = std::format!("{} ({})", env!("CARGO_PKG_VERSION"), env!("GIT_VERSION")); assert_eq!(version.1, check_version); } #[test] fn test_rest_api_settings() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/config/settings"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let settings = http_api_msg::api_msg_parse_key_value(&body); assert!(settings.is_ok()); let mut settings = std::collections::HashMap::new(); settings.insert("key1".to_string(), "value1".to_string()); settings.insert("key2".to_string(), "value2".to_string()); let body = http_api_msg::api_msg_gen_key_value(&settings); let c = client.put("/api/config/settings", body.as_str()); assert!(c.is_ok()); let (code, _) = c.unwrap(); assert_eq!(code, 204); let c = client.get("/api/config/settings"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let settings = http_api_msg::api_msg_parse_key_value(&body); assert!(settings.is_ok()); let settings = settings.unwrap(); assert_eq!(settings.len(), 7); assert_eq!(settings["key1"], "value1"); } #[test] fn test_rest_api_get_client() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::INFO); assert!(server.start().is_ok()); for i in 0..1024 { let mut request = TestDnsRequest::new(); request.domain = format!("{}.com", i); request.remote_addr = format!("client-{}", i); request.remote_mac = [1, 2, 3, 4, 5, i as u8]; request.id = i as u16; assert!(server.send_test_dnsrequest(request).is_ok()); } let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/client?page_size=4096"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let list = http_api_msg::api_msg_parse_client_list(&body); assert!(list.is_ok()); let list = list.unwrap(); assert_eq!(list.len(), 1024); } #[test] fn test_rest_api_stats_top() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); for i in 0..1024 { let mut request = TestDnsRequest::new(); if i < 512 { request.domain = format!("a.com"); request.remote_addr = format!("192.168.1.1"); } else if i < 512 + 256 + 128 { request.domain = format!("b.com"); request.remote_addr = format!("192.168.1.2"); } else { request.domain = format!("c.com"); request.remote_addr = format!("192.168.1.3"); } assert!(server.send_test_dnsrequest(request).is_ok()); } server.get_data_server().get_stat().refresh(); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/stats/top/client"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let list = http_api_msg::api_msg_parse_top_client_list(&body); assert!(list.is_ok()); let list = list.unwrap(); assert_eq!(list.len(), 3); assert_eq!(list[0].client_ip, "192.168.1.1"); assert_eq!(list[0].count, 512); assert_eq!(list[2].client_ip, "192.168.1.3"); assert_eq!(list[2].count, 128); let c = client.get("/api/stats/top/domain"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let list = http_api_msg::api_msg_parse_top_domain_list(&body); assert!(list.is_ok()); let list = list.unwrap(); assert_eq!(list.len(), 3); assert_eq!(list[0].domain, "a.com"); assert_eq!(list[0].count, 512); assert_eq!(list[2].domain, "c.com"); assert_eq!(list[2].count, 128); } #[test] fn test_rest_api_stats_overview() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); server.enable_mock_server(); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let server_name; unsafe { smartdns_ui::smartdns::smartdns_c::dns_stats .avg_time .avg_time = 22.0 as f32; smartdns_ui::smartdns::smartdns_c::dns_stats .request .blocked_count = 10; smartdns_ui::smartdns::smartdns_c::dns_stats.request.total = 15; server_name = smartdns_ui::smartdns::smartdns_get_server_name(); } let c = client.get("/api/stats/overview"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let overview = http_api_msg::api_msg_parse_stats_overview(&body); assert!(overview.is_ok()); let overview = overview.unwrap(); assert_eq!(overview.db_size > 0, true); assert_eq!(overview.server_name, server_name); } #[test] fn test_rest_api_stats_metrics() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); server.enable_mock_server(); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); unsafe { smartdns_ui::smartdns::smartdns_c::dns_stats .avg_time .avg_time = 22.0 as f32; smartdns_ui::smartdns::smartdns_c::dns_stats .request .blocked_count = 10; smartdns_ui::smartdns::smartdns_c::dns_stats.request.total = 15; } for i in 0..1024 { let mut request = TestDnsRequest::new(); request.domain = format!("{}.com", i); request.remote_addr = format!("client-{}", i); request.is_blocked = i % 2 == 0; assert!(server.send_test_dnsrequest(request).is_ok()); } let c = client.get("/api/stats/metrics"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let metrics = http_api_msg::api_msg_parse_metrics_data(&body); assert!(metrics.is_ok()); let metrics = metrics.unwrap(); assert_eq!(metrics.avg_query_time, 22.0 as f64); assert_eq!(metrics.cache_hit_rate, 0 as f64); assert_eq!(metrics.total_query_count, 1024); assert_eq!(metrics.block_query_count, 1024 / 2); } #[test] fn test_rest_api_get_hourly_query_count() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); assert!(server.start().is_ok()); for i in 0..1024 { let mut request = TestDnsRequest::new(); request.domain = format!("{}.com", i); request.remote_addr = format!("client-{}", i); assert!(server.send_test_dnsrequest(request).is_ok()); } let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); let c = client.get("/api/stats/hourly-query-count"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let list = http_api_msg::api_msg_parse_hourly_query_count(&body); assert!(list.is_ok()); let list = list.unwrap(); assert_eq!(list.hourly_query_count.len(), 1); assert_eq!(list.hourly_query_count[0].query_count, 1024); } #[test] fn test_rest_api_server_status() { let mut server = common::TestServer::new(); server.set_log_level(LogLevel::DEBUG); server.enable_mock_server(); assert!(server.start().is_ok()); let mut client = common::TestClient::new(&server.get_host()); let res = client.login("admin", "password"); assert!(res.is_ok()); unsafe { let server_type = smartdns_ui::smartdns::smartdns_c::dns_server_type_t_DNS_SERVER_UDP; let mut flags: smartdns_ui::smartdns::smartdns_c::client_dns_server_flags = std::mem::zeroed(); let ip = CString::new("1.2.3.4").expect("CString::new failed"); let port = 3353; smartdns_ui::smartdns::smartdns_c::dns_client_add_server( ip.as_ptr() as *const c_char, port, server_type, &mut flags, ); } let c = client.get("/api/upstream-server"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); let server_list = http_api_msg::api_msg_parse_upstream_server_list(&body); assert!(server_list.is_ok()); let server_list = server_list.unwrap(); assert!(server_list.len() > 0); let exists = server_list.iter().any(|server| server.ip == "1.2.3.4"); assert!(exists); } ================================================ FILE: src/.gitignore ================================================ .vscode .o .DS_Store .swp. smartdns !smartdns/ ================================================ FILE: src/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 SMARTDNS_LIB=libsmartdns.a SMARTDNS_TEST_LIB=libsmartdns-test.a # libs without main.o OBJS=$(filter-out main.o, $(patsubst %.c,%.o,$(wildcard *.c)) $(patsubst %.c,%.o,$(wildcard */*.c))) # libs without lib/%.o LINT_OBJS=$(filter-out lib/%.o, $(OBJS)) TEST_OBJS=$(patsubst %.o,%_test.o,$(OBJS)) MAIN_OBJ = main.o ifeq ($(filter -j,$(MAKEFLAGS)),) MAKEFLAGS += -j$(shell nproc) endif # cflags ifndef CFLAGS ifdef DEBUG CFLAGS = -g -DDEBUG else CFLAGS = -O2 endif CFLAGS +=-Wall -Wstrict-prototypes -fno-omit-frame-pointer -Wstrict-aliasing -funwind-tables -Wmissing-prototypes -Wshadow -Wextra -Wno-unused-parameter -Wno-implicit-fallthrough endif HASH := \# HAS_UNWIND := $(shell printf '$(HASH)include \nvoid main() { _Unwind_Backtrace(0, 0);}' | $(CC) -x c - -o /dev/null >/dev/null 2>&1 && echo -n 1 || echo -n 0) ifeq ($(HAS_UNWIND), 1) override CFLAGS += -DHAVE_UNWIND_BACKTRACE endif override CFLAGS +=-Iinclude override CFLAGS += -DBASE_FILE_NAME='"$(notdir $<)"' override CFLAGS += $(EXTRA_CFLAGS) ifdef VER override CFLAGS += -DSMARTDNS_VERION='"$(VER)"' endif HAS_GIT := $(shell command -v git 2> /dev/null) ifndef NO_GIT_VER ifdef HAS_GIT IS_GIT_REPO := $(shell git rev-parse --is-inside-work-tree 2>/dev/null) ifdef IS_GIT_REPO override COMMIT_VERION = $(shell git describe --tags --always --dirty) endif endif endif ifdef COMMIT_VERION override CFLAGS += -DCOMMIT_VERION='"$(shell git describe --tags --always --dirty)"' endif CXXFLAGS=-O2 -g -Wall -std=c++11 override CXXFLAGS +=-Iinclude ifeq ($(STATIC), yes) STATIC = 1 endif ifdef STATIC override CFLAGS += -DBUILD_STATIC override LDFLAGS += -lssl -lcrypto -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -ldl -lm -static -rdynamic else override LDFLAGS += -lssl -lcrypto -lpthread -ldl -lm -rdynamic endif USE_ATOMIC := $(shell printf '$(HASH)include \nvoid main() { uint64_t value=0;__atomic_add_fetch(&value, 1, __ATOMIC_SEQ_CST);__atomic_load_n(&value,__ATOMIC_SEQ_CST);}' | $(CC) -x c - -o /dev/null >/dev/null 2>&1 && echo -n 1 || echo -n 0) ifeq ($(USE_ATOMIC), 0) override CFLAGS += -DUSE_ATOMIC override LDFLAGS += -latomic endif override LDFLAGS += $(EXTRA_LDFLAGS) .PHONY: all clean all: $(BIN) %_test.o: %.c $(CC) -DTEST $(CFLAGS) -c $< -o $@ $(BIN) : $(MAIN_OBJ) $(SMARTDNS_LIB) $(CC) $^ -o $@ $(LDFLAGS) $(SMARTDNS_TEST_LIB): $(TEST_OBJS) $(AR) rcs $@ $^ $(SMARTDNS_LIB): $(OBJS) $(AR) rcs $@ $^ clang-tidy-parallel: @echo "Running clang-tidy with $(shell nproc) parallel jobs..." @printf '%s\n' $(LINT_OBJS:.o=.c) | xargs -P $(shell nproc) -I {} clang-tidy {} -- $(CFLAGS) clang-tidy: clang-tidy -p=. $(LINT_OBJS:.o=.c) -- $(CFLAGS) clean: $(RM) $(OBJS) $(BIN) $(SMARTDNS_LIB) $(MAIN_OBJ) $(SMARTDNS_TEST_LIB) $(TEST_OBJS) # lint specific files # make lint-files FILES="dns.c proxy.c" lint-files: @echo "Specify FILES variable to lint specific files, e.g., make lint-files FILES='src/dns.c src/proxy.c'" clang-tidy -p=. $(FILES) -- $(CFLAGS) ================================================ FILE: src/dns.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/dns.h" #include "smartdns/lib/stringutil.h" #include "smartdns/tlog.h" #include #include #include #include #include #define QR_MASK 0x8000 #define OPCODE_MASK 0x7800 #define AA_MASK 0x0400 #define TC_MASK 0x0200 #define RD_MASK 0x0100 #define RA_MASK 0x0080 #define Z_MASK 0x0040 #define AD_MASK 0x0020 #define CD_MASK 0x0010 #define RCODE_MASK 0x000F #define DNS_RR_END (0XFFFF) #define UNUSED(expr) \ do { \ (void)(expr); \ } while (0) #define member_size(type, member) sizeof(((type *)0)->member) static int is_aligned(void *ptr, int aligment) { return ((uintptr_t)(ptr)) % aligment == 0; } /* read short and move pointer */ static unsigned short _dns_read_short(unsigned char **buffer) { unsigned short value = 0; if (is_aligned(*buffer, 2)) { value = *((unsigned short *)(*buffer)); } else { memcpy(&value, *buffer, 2); } *buffer += 2; return ntohs(value); } /* write char and move pointer */ static __attribute__((unused)) void _dns_write_char(unsigned char **buffer, unsigned char value) { **buffer = value; *buffer += 1; } /* read char and move pointer */ static unsigned char _dns_read_char(unsigned char **buffer) { unsigned char value = **buffer; *buffer += 1; return value; } /* write short and move pointer */ static void _dns_write_short(unsigned char **buffer, unsigned short value) { value = htons(value); if (is_aligned(*buffer, 2)) { *((unsigned short *)(*buffer)) = value; } else { memcpy(*buffer, &value, 2); } *buffer += 2; } /* write short and move pointer */ static void _dns_write_shortptr(unsigned char **buffer, void *ptrvalue) { unsigned short value; if ((uintptr_t)ptrvalue % 2 == 0) { value = *(unsigned short *)ptrvalue; } else { memcpy(&value, ptrvalue, 2); } value = htons(value); if (is_aligned(*buffer, 2)) { *((unsigned short *)(*buffer)) = value; } else { memcpy(*buffer, &value, 2); } *buffer += 2; } /* write int and move pointer */ static void _dns_write_int(unsigned char **buffer, unsigned int value) { value = htonl(value); if (is_aligned(*buffer, 4)) { *((unsigned int *)(*buffer)) = value; } else { memcpy(*buffer, &value, 4); } *buffer += 4; } /* write int and move pointer */ static void _dns_write_intptr(unsigned char **buffer, void *ptrvalue) { unsigned int value; if ((uintptr_t)ptrvalue % 4 == 0) { value = *(unsigned int *)ptrvalue; } else { memcpy(&value, ptrvalue, 4); } value = htonl(value); if (is_aligned(*buffer, 4)) { *((unsigned int *)(*buffer)) = value; } else { memcpy(*buffer, &value, 4); } *buffer += 4; } /* read int and move pointer */ static unsigned int _dns_read_int(unsigned char **buffer) { unsigned int value = 0; if (is_aligned(*buffer, 4)) { value = *((unsigned int *)(*buffer)); } else { memcpy(&value, *buffer, 4); } *buffer += 4; return ntohl(value); } static inline int _dns_left_len(struct dns_context *context) { return context->maxsize - (context->ptr - context->data); } static int _dns_get_domain_from_packet(unsigned char *packet, int packet_size, unsigned char **domain_ptr, char *output, int size) { int output_len = 0; int copy_len = 0; int len = 0; unsigned char *ptr = *domain_ptr; int is_compressed = 0; int ptr_jump = 0; /*[len]string[len]string...[0]0 */ while (1) { if (ptr >= packet + packet_size || ptr < packet || output_len >= size - 1 || ptr_jump > 32) { return -1; } len = *ptr; if (len == 0) { *output = 0; ptr++; break; } /* compressed domain */ if (len >= 0xC0) { if ((ptr + 2) > (packet + packet_size)) { return -1; } /* 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 1 1| OFFSET | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ /* read offset */ len = _dns_read_short(&ptr) & 0x3FFF; if (is_compressed == 0) { *domain_ptr = ptr; } ptr = packet + len; if (ptr > packet + packet_size) { tlog(TLOG_DEBUG, "length is not enough %u:%ld, %p, %p", packet_size, (long)(ptr - packet), *domain_ptr, packet); return -1; } is_compressed = 1; ptr_jump++; continue; } ptr_jump = 0; /* change [len] to '.' */ if (output_len > 0) { *output = '.'; output++; output_len += 1; } if (ptr > packet + packet_size) { tlog(TLOG_DEBUG, "length is not enough %u:%ld, %p, %p", packet_size, (long)(ptr - packet), *domain_ptr, packet); return -1; } ptr++; if (output_len < size - 1) { /* copy sub string */ copy_len = (len < size - output_len) ? len : size - 1 - output_len; if ((ptr + copy_len) > (packet + packet_size)) { tlog(TLOG_DEBUG, "length is not enough %u:%ld, %p, %p", packet_size, (long)(ptr - packet), *domain_ptr, packet); return -1; } memcpy(output, ptr, copy_len); } ptr += len; output += len; output_len += len; } if (is_compressed == 0) { *domain_ptr = ptr; } return 0; } static int _dns_decode_domain(struct dns_context *context, char *output, int size) { return _dns_get_domain_from_packet(context->data, context->maxsize, &(context->ptr), output, size); } static unsigned int dict_hash(const char *s) { unsigned int hashval = 0; for (hashval = 0; *s != '\0'; s++) { hashval = *s + 31 * hashval; } return hashval; } static int _dns_add_domain_dict(struct dns_context *context, unsigned int hash, int pos) { struct dns_packet_dict *dict = context->namedict; if (dict->dict_count >= DNS_PACKET_DICT_SIZE) { return -1; } if (hash == 0) { return -1; } if (pos >= context->maxsize) { return -1; } int index = dict->dict_count; dict->names[index].hash = hash; dict->names[index].pos = pos; dict->dict_count++; return 0; } static int _dns_get_domain_offset(struct dns_context *context, const char *domain) { int i = 0; char domain_check[DNS_MAX_CNAME_LEN]; struct dns_packet_dict *dict = context->namedict; if (*domain == '\0') { return -1; } unsigned int hash = dict_hash(domain); for (i = 0; i < dict->dict_count; i++) { if (dict->names[i].hash != hash) { continue; } unsigned char *domain_check_ptr = dict->names[i].pos + context->data; if (_dns_get_domain_from_packet(context->data, context->maxsize, &domain_check_ptr, domain_check, DNS_MAX_CNAME_LEN) != 0) { return -1; } return dict->names[i].pos; } _dns_add_domain_dict(context, hash, context->ptr - 1 - context->data); return -1; } static int _dns_encode_domain(struct dns_context *context, const char *domain) { int num = 0; int total_len = 0; unsigned char *ptr_num = context->ptr++; int dict_offset = 0; dict_offset = _dns_get_domain_offset(context, domain); total_len++; /*[len]string[len]string...[0]0 */ while (_dns_left_len(context) > 1 && *domain != 0) { total_len++; if (dict_offset >= 0) { int offset = 0xc000 | dict_offset; if (_dns_left_len(context) < 2) { return -1; } _dns_write_short(&ptr_num, offset); context->ptr++; ptr_num = NULL; return total_len; } if (*domain == '.') { *ptr_num = num; num = 0; ptr_num = context->ptr; domain++; context->ptr++; dict_offset = _dns_get_domain_offset(context, domain); continue; } *context->ptr = *domain; num++; context->ptr++; domain++; } if (_dns_left_len(context) < 1) { return -1; } *ptr_num = num; if (total_len > 1) { /* if domain is '\0', [domain] is '\0' */ *(context->ptr) = 0; total_len++; context->ptr++; } if (_dns_left_len(context) <= 0) { return -1; } return total_len; } /* iterator get rrs begin */ struct dns_rrs *dns_get_rrs_start(struct dns_packet *packet, dns_rr_type type, int *count) { unsigned short start = 0; struct dns_head *head = &packet->head; /* get rrs count by rrs type */ switch (type) { case DNS_RRS_QD: *count = head->qdcount; start = packet->questions; break; case DNS_RRS_AN: *count = head->ancount; start = packet->answers; break; case DNS_RRS_NS: *count = head->nscount; start = packet->nameservers; break; case DNS_RRS_NR: *count = head->nrcount; start = packet->additional; break; case DNS_RRS_OPT: *count = packet->optcount; start = packet->optional; break; default: return NULL; break; } /* if not resource record, return null */ if (start == DNS_RR_END) { return NULL; } /* return rrs data start address */ return (struct dns_rrs *)(packet->data + start); } /* iterator next rrs */ struct dns_rrs *dns_get_rrs_next(struct dns_packet *packet, struct dns_rrs *rrs) { if (rrs->next == DNS_RR_END) { return NULL; } return (struct dns_rrs *)(packet->data + rrs->next); } static void _dns_init_context_by_rrs(struct dns_rrs *rrs, struct dns_context *context) { context->packet = rrs->packet; context->data = rrs->packet->data; context->ptr = rrs->data; context->namedict = &rrs->packet->namedict; context->maxsize = rrs->data - rrs->packet->data + rrs->len; } /* iterator add rrs begin */ static int _dns_add_rrs_start(struct dns_packet *packet, struct dns_context *context) { struct dns_rrs *rrs = NULL; unsigned char *end = packet->data + packet->len; if ((packet->len + (int)sizeof(*rrs)) >= packet->size) { return -1; } rrs = (struct dns_rrs *)end; context->ptr = rrs->data; context->packet = packet; context->maxsize = packet->size - sizeof(*packet); context->data = packet->data; context->namedict = &packet->namedict; return 0; } /* iterator add rrs end */ static int _dns_rr_add_end(struct dns_packet *packet, int type, dns_type_t rtype, int len) { struct dns_rrs *rrs = NULL; struct dns_rrs *rrs_next = NULL; struct dns_head *head = &packet->head; unsigned char *end = packet->data + packet->len; unsigned short *count = NULL; unsigned short *start = NULL; rrs = (struct dns_rrs *)end; if (packet->len + len > packet->size - (int)sizeof(*packet) - (int)sizeof(*rrs)) { return -1; } switch (type) { case DNS_RRS_QD: count = &head->qdcount; start = &packet->questions; break; case DNS_RRS_AN: count = &head->ancount; start = &packet->answers; break; case DNS_RRS_NS: count = &head->nscount; start = &packet->nameservers; break; case DNS_RRS_NR: count = &head->nrcount; start = &packet->additional; break; case DNS_RRS_OPT: count = &packet->optcount; start = &packet->optional; break; default: return -1; break; } /* add data to end of dns_packet, and set previous rrs point to this rrs */ if (*start != DNS_RR_END) { rrs_next = (struct dns_rrs *)(packet->data + *start); while (rrs_next->next != DNS_RR_END) { rrs_next = (struct dns_rrs *)(packet->data + rrs_next->next); } rrs_next->next = packet->len; } else { *start = packet->len; } /* update rrs head info */ rrs->packet = packet; rrs->len = len; rrs->type = rtype; rrs->next = DNS_RR_END; /* update total data length */ *count += 1; packet->len += len + sizeof(*rrs); return 0; } static int _dns_add_qr_head(struct dns_context *context, const char *domain, int qtype, int qclass) { int ret = _dns_encode_domain(context, domain); if (ret < 0) { return -1; } if (_dns_left_len(context) < 4) { return -1; } _dns_write_short(&context->ptr, qtype); _dns_write_short(&context->ptr, qclass); return ret + 4; } static int _dns_get_qr_head(struct dns_context *context, char *domain, int maxsize, int *qtype, int *qclass) { int ret = 0; if (domain == NULL || context == NULL) { return -1; } ret = _dns_decode_domain(context, domain, maxsize); if (ret < 0) { return -1; } if (_dns_left_len(context) < 4) { return -1; } *qtype = _dns_read_short(&context->ptr); *qclass = _dns_read_short(&context->ptr); return 0; } static int _dns_add_rr_head(struct dns_context *context, const char *domain, int qtype, int qclass, int ttl, int rr_len) { int len = 0; /* resource record head */ /* |domain | * |qtype | qclass | * | ttl | * | rrlen | rrdata | */ len = _dns_add_qr_head(context, domain, qtype, qclass); if (len < 0) { return -1; } if (_dns_left_len(context) < 6) { return -1; } _dns_write_int(&context->ptr, ttl); _dns_write_short(&context->ptr, rr_len); return len + 6; } static int _dns_get_rr_head(struct dns_context *context, char *domain, int maxsize, int *qtype, int *qclass, int *ttl, int *rr_len) { int len = 0; /* resource record head */ /* |domain | * |qtype | qclass | * | ttl | * | rrlen | rrdata | */ len = _dns_get_qr_head(context, domain, maxsize, qtype, qclass); if (_dns_left_len(context) < 6) { return -1; } *ttl = _dns_read_int(&context->ptr); *rr_len = _dns_read_short(&context->ptr); return len; } struct dns_rr_nested *dns_add_rr_nested_start(struct dns_rr_nested *rr_nested_buffer, struct dns_packet *packet, dns_rr_type type, dns_type_t rtype, const char *domain, int ttl) { int len = 0; memset(rr_nested_buffer, 0, sizeof(*rr_nested_buffer)); rr_nested_buffer->type = type; rr_nested_buffer->rtype = rtype; int ret = 0; /* resource record */ /* |domain | * |qtype | qclass | * | ttl | * | rrlen | rrdata | */ ret = _dns_add_rrs_start(packet, &rr_nested_buffer->context); if (ret < 0) { return NULL; } rr_nested_buffer->rr_start = rr_nested_buffer->context.ptr; /* add rr head */ len = _dns_add_rr_head(&rr_nested_buffer->context, domain, rtype, DNS_C_IN, ttl, 0); if (len < 0) { return NULL; } rr_nested_buffer->rr_len_ptr = rr_nested_buffer->context.ptr - 2; rr_nested_buffer->rr_head_len = len; return rr_nested_buffer; } int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, const void *data, int data_len) { if (rr_nested == NULL || data == NULL || data_len <= 0) { return -1; } if (_dns_left_len(&rr_nested->context) < data_len) { return -1; } if (is_aligned(rr_nested->context.ptr, 2) == 0) { return -1; } memcpy(rr_nested->context.ptr, data, data_len); rr_nested->context.ptr += data_len; if (is_aligned(rr_nested->context.ptr, 2) == 0) { if (_dns_left_len(&rr_nested->context) < 1) { return -1; } _dns_write_char(&rr_nested->context.ptr, 0); } return 0; } int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested) { if (rr_nested == NULL || rr_nested->rr_start == NULL) { return -1; } int len = rr_nested->context.ptr - rr_nested->rr_start; unsigned char *ptr = rr_nested->rr_len_ptr; if (ptr == NULL || _dns_left_len(&rr_nested->context) < 2) { return -1; } /* NO SVC keys, reset ptr */ if (len <= 14) { rr_nested->context.ptr = rr_nested->rr_start; return 0; } _dns_write_short(&ptr, len - rr_nested->rr_head_len); return _dns_rr_add_end(rr_nested->context.packet, rr_nested->type, rr_nested->rtype, len); } void *dns_get_rr_nested_start(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *ttl, int *rr_len) { struct dns_context data_context; int qclass = 0; int ret = 0; _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, qtype, &qclass, ttl, rr_len); if (ret < 0) { return NULL; } if (qclass != DNS_C_IN) { return NULL; } if (*rr_len < 2) { return NULL; } return data_context.ptr; } void *dns_get_rr_nested_next(struct dns_rrs *rrs, void *rr_nested, int rr_nested_len) { void *end = rrs->data + rrs->len; void *p = rr_nested + rr_nested_len; if (is_aligned(p, 2) == 0) { p++; } if (p == end) { return NULL; } else if (p > end) { return NULL; } return p; } static int _dns_add_RAW(struct dns_packet *packet, dns_rr_type rrtype, dns_type_t rtype, const char *domain, int ttl, const void *raw, int raw_len) { int len = 0; struct dns_context context; int ret = 0; if (raw_len < 0) { return -1; } /* resource record */ /* |domain | * |qtype | qclass | * | ttl | * | rrlen | rrdata | */ ret = _dns_add_rrs_start(packet, &context); if (ret < 0) { return -1; } /* add rr head */ len = _dns_add_rr_head(&context, domain, rtype, DNS_C_IN, ttl, raw_len); if (len < 0) { return -1; } if (_dns_left_len(&context) < raw_len) { return -1; } /* add rr data */ memcpy(context.ptr, raw, raw_len); context.ptr += raw_len; len += raw_len; return _dns_rr_add_end(packet, rrtype, rtype, len); } static int _dns_get_RAW(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, void *raw, int *raw_len) { int qtype = 0; int qclass = 0; int rr_len = 0; int ret = 0; struct dns_context context; /* resource record head */ /* |domain | * |qtype | qclass | * | ttl | * | rrlen | rrdata | */ _dns_init_context_by_rrs(rrs, &context); /* get rr head */ ret = _dns_get_rr_head(&context, domain, maxsize, &qtype, &qclass, ttl, &rr_len); if (ret < 0) { return -1; } if (qtype != rrs->type || rr_len > *raw_len) { return -1; } /* get rr data */ memcpy(raw, context.ptr, rr_len); context.ptr += rr_len; *raw_len = rr_len; return 0; } static int _dns_add_opt_RAW(struct dns_packet *packet, dns_opt_code_t opt_rrtype, void *raw, int raw_len) { unsigned char opt_data[DNS_MAX_OPT_LEN]; struct dns_opt *opt = (struct dns_opt *)opt_data; int len = 0; opt->code = opt_rrtype; opt->length = sizeof(unsigned short); memcpy(opt->data, raw, raw_len); len += raw_len; len += sizeof(*opt); return _dns_add_RAW(packet, DNS_RRS_OPT, (dns_type_t)opt_rrtype, "", 0, opt_data, len); } static int _dns_get_opt_RAW(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, struct dns_opt *dns_opt, int *dns_optlen) { *dns_optlen = DNS_MAX_OPT_LEN; return _dns_get_RAW(rrs, domain, maxsize, ttl, dns_opt, dns_optlen); } static int __attribute__((unused)) _dns_add_OPT(struct dns_packet *packet, dns_rr_type type, unsigned short opt_code, unsigned short opt_len, struct dns_opt *opt) { int ret = 0; int len = 0; struct dns_context context; int total_len = sizeof(*opt) + opt->length; int ttl = 0; /* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0: | OPTION-CODE | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2: | OPTION-LENGTH | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4: | | / OPTION-DATA / / / +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ ret = _dns_add_rrs_start(packet, &context); if (ret < 0) { return -1; } if (_dns_left_len(&context) < total_len) { return -1; } ttl = (opt_code << 16) | opt_len; /* add rr head */ len = _dns_add_rr_head(&context, "", type, DNS_C_IN, ttl, total_len); if (len < 0) { return -1; } /* add rr data */ memcpy(context.ptr, opt, total_len); context.ptr += total_len; len = context.ptr - context.data - packet->len; return _dns_rr_add_end(packet, type, DNS_T_OPT, len); } static int __attribute__((unused)) _dns_get_OPT(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len, struct dns_opt *opt, int *opt_maxlen) { int qtype = 0; int qclass = 0; int rr_len = 0; int ret = 0; struct dns_context context; char domain[DNS_MAX_CNAME_LEN]; int maxsize = DNS_MAX_CNAME_LEN; int ttl = 0; _dns_init_context_by_rrs(rrs, &context); /* get rr head */ ret = _dns_get_rr_head(&context, domain, maxsize, &qtype, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } if (qtype != rrs->type || rr_len > *opt_len) { return -1; } /* get rr data */ *opt_code = ttl >> 16; *opt_len = ttl & 0xFFFF; memcpy(opt, context.ptr, rr_len); context.ptr += rr_len; *opt_maxlen = rr_len; return 0; } int dns_add_CNAME(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *cname) { int rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1; return _dns_add_RAW(packet, type, DNS_T_CNAME, domain, ttl, cname, rr_len); } int dns_get_CNAME(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size) { int len = cname_size; return _dns_get_RAW(rrs, domain, maxsize, ttl, cname, &len); } int dns_add_A(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, unsigned char addr[DNS_RR_A_LEN]) { return _dns_add_RAW(packet, type, DNS_T_A, domain, ttl, addr, DNS_RR_A_LEN); } int dns_get_A(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsigned char addr[DNS_RR_A_LEN]) { int len = DNS_RR_A_LEN; return _dns_get_RAW(rrs, domain, maxsize, ttl, addr, &len); } int dns_add_PTR(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *cname) { int rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1; return _dns_add_RAW(packet, type, DNS_T_PTR, domain, ttl, cname, rr_len); } int dns_get_PTR(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size) { int len = cname_size; return _dns_get_RAW(rrs, domain, maxsize, ttl, cname, &len); } int dns_add_TXT(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *text) { int rr_len = strnlen(text, DNS_MAX_CNAME_LEN); char data[DNS_MAX_CNAME_LEN]; if (rr_len > DNS_MAX_CNAME_LEN - 2) { return -1; } data[0] = rr_len; rr_len++; memcpy(data + 1, text, rr_len); data[rr_len] = 0; return _dns_add_RAW(packet, type, DNS_T_TXT, domain, ttl, data, rr_len); } int dns_get_TXT(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *text, int txt_size) { return -1; } int dns_add_NS(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *cname) { int rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1; return _dns_add_RAW(packet, type, DNS_T_NS, domain, ttl, cname, rr_len); } int dns_get_NS(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size) { int len = cname_size; return _dns_get_RAW(rrs, domain, maxsize, ttl, cname, &len); } int dns_add_AAAA(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, unsigned char addr[DNS_RR_AAAA_LEN]) { return _dns_add_RAW(packet, type, DNS_T_AAAA, domain, ttl, addr, DNS_RR_AAAA_LEN); } int dns_get_AAAA(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsigned char addr[DNS_RR_AAAA_LEN]) { int len = DNS_RR_AAAA_LEN; return _dns_get_RAW(rrs, domain, maxsize, ttl, addr, &len); } int dns_add_SOA(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, struct dns_soa *soa) { /* SOA */ /*| mname | *| rname | *| serial | *| refresh | *| retry | *| expire | *| minimum | */ unsigned char data[sizeof(*soa)]; unsigned char *ptr = data; int len = 0; if (soa == NULL || domain == NULL || packet == NULL) { return -1; } safe_strncpy((char *)ptr, soa->mname, DNS_MAX_CNAME_LEN); ptr += strnlen(soa->mname, DNS_MAX_CNAME_LEN - 1) + 1; safe_strncpy((char *)ptr, soa->rname, DNS_MAX_CNAME_LEN); ptr += strnlen(soa->rname, DNS_MAX_CNAME_LEN - 1) + 1; memcpy(ptr, &soa->serial, sizeof(unsigned int)); ptr += 4; memcpy(ptr, &soa->refresh, sizeof(unsigned int)); ptr += 4; memcpy(ptr, &soa->retry, sizeof(unsigned int)); ptr += 4; memcpy(ptr, &soa->expire, sizeof(unsigned int)); ptr += 4; memcpy(ptr, &soa->minimum, sizeof(unsigned int)); ptr += 4; len = ptr - data; return _dns_add_RAW(packet, type, DNS_T_SOA, domain, ttl, data, len); } int dns_get_SOA(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, struct dns_soa *soa) { unsigned char data[sizeof(*soa)]; unsigned char *ptr = data; int len = sizeof(data); /* SOA */ /*| mname | *| rname | *| serial | *| refresh | *| retry | *| expire | *| minimum | */ if (_dns_get_RAW(rrs, domain, maxsize, ttl, data, &len) != 0) { return -1; } safe_strncpy(soa->mname, (char *)ptr, DNS_MAX_CNAME_LEN - 1); ptr += strnlen(soa->mname, DNS_MAX_CNAME_LEN - 1) + 1; if (ptr - data >= len) { return -1; } safe_strncpy(soa->rname, (char *)ptr, DNS_MAX_CNAME_LEN - 1); ptr += strnlen(soa->rname, DNS_MAX_CNAME_LEN - 1) + 1; if (ptr - data + 20 > len) { return -1; } memcpy(&soa->serial, ptr, 4); ptr += 4; memcpy(&soa->refresh, ptr, 4); ptr += 4; memcpy(&soa->retry, ptr, 4); ptr += 4; memcpy(&soa->expire, ptr, 4); ptr += 4; memcpy(&soa->minimum, ptr, 4); return 0; } int dns_set_OPT_payload_size(struct dns_packet *packet, int payload_size) { if (payload_size < 512) { payload_size = 512; } packet->payloadsize = payload_size; return 0; } int dns_get_OPT_payload_size(struct dns_packet *packet) { return packet->payloadsize; } int dns_set_OPT_option(struct dns_packet *packet, unsigned int option) { packet->opt_option = option; return 0; } unsigned int dns_get_OPT_option(struct dns_packet *packet) { return packet->opt_option; } int dns_add_OPT_ECS(struct dns_packet *packet, struct dns_opt_ecs *ecs) { unsigned char opt_data[DNS_MAX_OPT_LEN]; struct dns_opt *opt = (struct dns_opt *)opt_data; int len = 0; /* ecs size 4 + bit of address*/ len = 4; len += (ecs->source_prefix / 8); len += (ecs->source_prefix % 8 > 0) ? 1 : 0; opt->length = len; opt->code = DNS_OPT_T_ECS; memcpy(opt->data, ecs, len); len += sizeof(*opt); return _dns_add_RAW(packet, DNS_RRS_OPT, (dns_type_t)DNS_OPT_T_ECS, "", 0, opt_data, len); } int dns_get_OPT_ECS(struct dns_rrs *rrs, struct dns_opt_ecs *ecs) { unsigned char opt_data[DNS_MAX_OPT_LEN]; char domain[DNS_MAX_CNAME_LEN] = {0}; struct dns_opt *opt = (struct dns_opt *)opt_data; int len = DNS_MAX_OPT_LEN; int ttl = 0; if (_dns_get_RAW(rrs, domain, DNS_MAX_CNAME_LEN, &ttl, opt_data, &len) != 0) { return -1; } if (len < (int)sizeof(*opt)) { return -1; } if (opt->code != DNS_OPT_T_ECS) { return -1; } memcpy(ecs, opt->data, opt->length); return 0; } int dns_add_OPT_TCP_KEEPALIVE(struct dns_packet *packet, unsigned short timeout) { unsigned short timeout_net = htons(timeout); int data_len = 0; if (timeout > 0) { data_len = sizeof(timeout); } return _dns_add_opt_RAW(packet, DNS_OPT_T_TCP_KEEPALIVE, &timeout_net, data_len); } int dns_get_OPT_TCP_KEEPALIVE(struct dns_rrs *rrs, unsigned short *timeout) { unsigned char opt_data[DNS_MAX_OPT_LEN]; char domain[DNS_MAX_CNAME_LEN] = {0}; struct dns_opt *opt = (struct dns_opt *)opt_data; int len = DNS_MAX_OPT_LEN; int ttl = 0; unsigned char *data = NULL; if (_dns_get_opt_RAW(rrs, domain, DNS_MAX_CNAME_LEN, &ttl, opt, &len) != 0) { return -1; } if (len < (int)sizeof(*opt)) { return -1; } if (opt->code != DNS_OPT_T_TCP_KEEPALIVE) { return -1; } if (opt->length == 0) { *timeout = 0; return 0; } if (opt->length != sizeof(unsigned short)) { return -1; } data = opt->data; *timeout = _dns_read_short(&data); return 0; } int dns_add_SRV(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, int priority, int weight, int port, const char *target) { unsigned char data[DNS_MAX_CNAME_LEN]; unsigned char *data_ptr = data; int target_len = 0; if (target == NULL) { target = ""; } target_len = strnlen(target, DNS_MAX_CNAME_LEN) + 1; memcpy(data_ptr, &priority, sizeof(unsigned short)); data_ptr += sizeof(unsigned short); memcpy(data_ptr, &weight, sizeof(unsigned short)); data_ptr += sizeof(unsigned short); memcpy(data_ptr, &port, sizeof(unsigned short)); data_ptr += sizeof(unsigned short); if (data_ptr - data + target_len >= DNS_MAX_CNAME_LEN) { return -1; } safe_strncpy((char *)data_ptr, target, target_len); data_ptr += target_len; return _dns_add_RAW(packet, type, DNS_T_SRV, domain, ttl, data, data_ptr - data); } int dns_get_SRV(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsigned short *priority, unsigned short *weight, unsigned short *port, char *target, int target_size) { unsigned char data[DNS_MAX_CNAME_LEN]; unsigned char *ptr = data; int len = sizeof(data); if (_dns_get_RAW(rrs, domain, maxsize, ttl, data, &len) != 0) { return -1; } if (len < 6) { return -1; } memcpy(priority, ptr, sizeof(unsigned short)); ptr += sizeof(unsigned short); memcpy(weight, ptr, sizeof(unsigned short)); ptr += sizeof(unsigned short); memcpy(port, ptr, sizeof(unsigned short)); ptr += sizeof(unsigned short); safe_strncpy(target, (char *)ptr, target_size); return 0; } static int _dns_add_SVCB_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type, dns_type_t rtype, const char *domain, int ttl, int priority, const char *target) { svcparam_buffer = dns_add_rr_nested_start(svcparam_buffer, packet, type, rtype, domain, ttl); if (svcparam_buffer == NULL) { return -1; } int target_len = 0; if (target == NULL || target[0] == '.') { target = ""; } target_len = strnlen(target, DNS_MAX_CNAME_LEN) + 1; if (_dns_left_len(&svcparam_buffer->context) < 2 + target_len) { return -1; } /* add rr data */ _dns_write_short(&svcparam_buffer->context.ptr, priority); safe_strncpy((char *)svcparam_buffer->context.ptr, target, target_len); svcparam_buffer->context.ptr += target_len; if (is_aligned(svcparam_buffer->context.ptr, 2) != 1) { if (_dns_left_len(&svcparam_buffer->context) < 1) { return -1; } _dns_write_char(&svcparam_buffer->context.ptr, 0); } return 0; } int dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, int priority, const char *target) { return _dns_add_SVCB_HTTPS_start(svcparam_buffer, packet, type, DNS_T_HTTPS, domain, ttl, priority, target); } int dns_add_SVCB_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, int priority, const char *target) { return _dns_add_SVCB_HTTPS_start(svcparam_buffer, packet, type, DNS_T_SVCB, domain, ttl, priority, target); } int dns_SVCB_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len) { if (_dns_left_len(&svcparam->context) < 2 + len) { return -1; } if (dns_add_rr_nested_memcpy(svcparam, &key, 2) != 0) { return -1; } if (dns_add_rr_nested_memcpy(svcparam, &len, 2) != 0) { return -1; } if (dns_add_rr_nested_memcpy(svcparam, value, len) != 0) { return -1; } return 0; } int dns_SVCB_add_port(struct dns_rr_nested *svcparam, unsigned short port) { if (_dns_left_len(&svcparam->context) < 6) { return -1; } unsigned short value = DNS_HTTPS_T_PORT; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } value = 2; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } value = htons(port); if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } return 0; } int dns_SVCB_add_alpn(struct dns_rr_nested *svcparam, const unsigned char *alpn, int alpn_len) { if (_dns_left_len(&svcparam->context) < 2 + 2 + alpn_len) { return -1; } unsigned short value = DNS_HTTPS_T_ALPN; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } value = alpn_len; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } if (dns_add_rr_nested_memcpy(svcparam, alpn, alpn_len) != 0) { return -1; } return 0; } int dns_SVCB_add_no_default_alpn(struct dns_rr_nested *svcparam) { if (_dns_left_len(&svcparam->context) < 4) { return -1; } unsigned short value = DNS_HTTPS_T_NO_DEFAULT_ALPN; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } value = 0; /* no data for no-default-alpn */ if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } return 0; } int dns_SVCB_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len) { if (_dns_left_len(&svcparam->context) < 2 + 2 + ech_len) { return -1; } unsigned short value = DNS_HTTPS_T_ECH; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } value = ech_len; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } if (dns_add_rr_nested_memcpy(svcparam, ech, ech_len) != 0) { return -1; } return 0; } int dns_SVCB_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num) { if (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_A_LEN) { return -1; } unsigned short value = DNS_HTTPS_T_IPV4HINT; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } value = addr_num * DNS_RR_A_LEN; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } for (int i = 0; i < addr_num; i++) { if (dns_add_rr_nested_memcpy(svcparam, addr[i], DNS_RR_A_LEN) != 0) { return -1; } } return 0; } int dns_SVCB_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num) { if (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_AAAA_LEN) { return -1; } unsigned short value = DNS_HTTPS_T_IPV6HINT; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } value = addr_num * DNS_RR_AAAA_LEN; if (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) { return -1; } for (int i = 0; i < addr_num; i++) { if (dns_add_rr_nested_memcpy(svcparam, addr[i], DNS_RR_AAAA_LEN) != 0) { return -1; } } return 0; } int dns_add_SVCB_end(struct dns_rr_nested *svcparam) { return dns_add_rr_nested_end(svcparam); } /* Backward compatibility wrapper */ int dns_add_HTTPS_end(struct dns_rr_nested *svcparam) { return dns_add_SVCB_end(svcparam); } /* Backward compatibility wrappers for dns_HTTPS_add_* functions */ int dns_HTTPS_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len) { return dns_SVCB_add_raw(svcparam, key, value, len); } int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alpn_len) { return dns_SVCB_add_alpn(svcparam, (const unsigned char *)alpn, alpn_len); } int dns_HTTPS_add_no_default_alpn(struct dns_rr_nested *svcparam) { return dns_SVCB_add_no_default_alpn(svcparam); } int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port) { return dns_SVCB_add_port(svcparam, port); } int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num) { return dns_SVCB_add_ipv4hint(svcparam, addr, addr_num); } int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len) { return dns_SVCB_add_ech(svcparam, ech, ech_len); } int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num) { return dns_SVCB_add_ipv6hint(svcparam, addr, addr_num); } int dns_svcparm_start(struct dns_rrs *rrs, struct dns_svcparam **https_param, char *domain, int maxsize, int *ttl, int *priority, char *target, int target_size) { int qtype = 0; unsigned char *data = NULL; int rr_len = 0; data = dns_get_rr_nested_start(rrs, domain, maxsize, &qtype, ttl, &rr_len); if (data == NULL) { return -1; } if (qtype != DNS_T_HTTPS && qtype != DNS_T_SVCB) { return -1; } if (rr_len < 2) { return -1; } *priority = _dns_read_short(&data); rr_len -= 2; if (rr_len <= 0) { return -1; } int len = strnlen((char *)data, rr_len); safe_strncpy(target, (char *)data, target_size); data += len + 1; rr_len -= len + 1; if (rr_len < 0) { return -1; } /* PADDING */ if (is_aligned(data, 2) != 1) { data++; rr_len--; } if (rr_len == 0) { *https_param = NULL; return 0; } *https_param = (struct dns_svcparam *)data; return 0; } struct dns_svcparam *dns_svcparm_next(struct dns_rrs *rrs, struct dns_svcparam *param) { return dns_get_rr_nested_next(rrs, param, sizeof(struct dns_svcparam) + param->len); } /* * Format: * |DNS_NAME\0(string)|qtype(short)|qclass(short)| */ int dns_add_domain(struct dns_packet *packet, const char *domain, int qtype, int qclass) { int len = 0; int ret = 0; struct dns_context context; ret = _dns_add_rrs_start(packet, &context); if (ret < 0) { return -1; } len = _dns_add_qr_head(&context, domain, qtype, qclass); if (len < 0) { return -1; } return _dns_rr_add_end(packet, DNS_RRS_QD, DNS_T_CNAME, len); } int dns_get_domain(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *qclass) { struct dns_context context; _dns_init_context_by_rrs(rrs, &context); return _dns_get_qr_head(&context, domain, maxsize, qtype, qclass); } static int _dns_decode_head(struct dns_context *context) { unsigned int fields = 0; int len = 12; struct dns_head *head = &context->packet->head; if (_dns_left_len(context) < len) { return -1; } /* 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ head->id = _dns_read_short(&context->ptr); fields = _dns_read_short(&context->ptr); head->qr = (fields & QR_MASK) >> 15; head->opcode = (fields & OPCODE_MASK) >> 11; head->aa = (fields & AA_MASK) >> 10; head->tc = (fields & TC_MASK) >> 9; head->rd = (fields & RD_MASK) >> 8; head->ra = (fields & RA_MASK) >> 7; head->z = (fields & Z_MASK) >> 6; head->ad = (fields & AD_MASK) >> 5; head->cd = (fields & CD_MASK) >> 4; head->rcode = (fields & RCODE_MASK) >> 0; head->qdcount = _dns_read_short(&context->ptr); head->ancount = _dns_read_short(&context->ptr); head->nscount = _dns_read_short(&context->ptr); head->nrcount = _dns_read_short(&context->ptr); return 0; } static int _dns_encode_head(struct dns_context *context) { int len = 12; struct dns_head *head = &context->packet->head; if (_dns_left_len(context) < len) { return -1; } _dns_write_short(&context->ptr, head->id); int fields = 0; fields |= (head->qr << 15) & QR_MASK; fields |= (head->opcode << 11) & OPCODE_MASK; fields |= (head->aa << 10) & AA_MASK; fields |= (head->tc << 9) & TC_MASK; fields |= (head->rd << 8) & RD_MASK; fields |= (head->ra << 7) & RA_MASK; fields |= (head->z << 6) & Z_MASK; fields |= (head->ad << 5) & AD_MASK; fields |= (head->cd << 4) & CD_MASK; fields |= (head->rcode << 0) & RCODE_MASK; _dns_write_short(&context->ptr, fields); _dns_write_short(&context->ptr, head->qdcount); _dns_write_short(&context->ptr, head->ancount); _dns_write_short(&context->ptr, head->nscount); _dns_write_short(&context->ptr, head->nrcount); return len; } static int _dns_encode_head_count(struct dns_context *context) { int len = 12; struct dns_head *head = &context->packet->head; unsigned char *ptr = context->data; ptr += 4; _dns_write_short(&ptr, head->qdcount); _dns_write_short(&ptr, head->ancount); _dns_write_short(&ptr, head->nscount); _dns_write_short(&ptr, head->nrcount); return len; } static int _dns_decode_qr_head(struct dns_context *context, char *domain, int domain_size, int *qtype, int *qclass) { int ret = 0; /* 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ ret = _dns_decode_domain(context, domain, domain_size); if (ret < 0) { tlog(TLOG_DEBUG, "decode domain failed."); return -1; } if (_dns_left_len(context) < 4) { tlog(TLOG_DEBUG, "left length is not enough, %s.", domain); return -1; } *qtype = _dns_read_short(&context->ptr); *qclass = _dns_read_short(&context->ptr); return 0; } static int _dns_encode_qr_head(struct dns_context *context, char *domain, int qtype, int qclass) { int ret = 0; ret = _dns_encode_domain(context, domain); if (ret < 0) { return -1; } if (_dns_left_len(context) < 4) { return -1; } _dns_write_short(&context->ptr, qtype); _dns_write_short(&context->ptr, qclass); return 0; } static int _dns_decode_rr_head(struct dns_context *context, char *domain, int domain_size, int *qtype, int *qclass, int *ttl, int *rr_len) { int len = 0; len = _dns_decode_qr_head(context, domain, domain_size, qtype, qclass); if (len < 0) { tlog(TLOG_DEBUG, "decode qr head failed."); return -1; } if (_dns_left_len(context) < 6) { tlog(TLOG_DEBUG, "left length is not enough."); return -1; } *ttl = _dns_read_int(&context->ptr); *rr_len = _dns_read_short(&context->ptr); if (*rr_len < 0 || *ttl < 0) { tlog(TLOG_DEBUG, "rr len or ttl is invalid."); return -1; } if (*rr_len > _dns_left_len(context)) { tlog(TLOG_DEBUG, "rr len exceeds remaining buffer."); return -1; } return 0; } static int _dns_encode_rr_head(struct dns_context *context, char *domain, int qtype, int qclass, int ttl, int rr_len, unsigned char **rr_len_ptr) { int ret = 0; ret = _dns_encode_qr_head(context, domain, qtype, qclass); if (ret < 0) { return -1; } if (_dns_left_len(context) < 6) { return -1; } _dns_write_int(&context->ptr, ttl); if (rr_len_ptr) { *rr_len_ptr = context->ptr; } _dns_write_short(&context->ptr, rr_len); return 0; } static int _dns_encode_raw(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; char domain[DNS_MAX_CNAME_LEN]; int rr_len = 0; unsigned char *rr_len_ptr = NULL; struct dns_context data_context; /* 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TTL | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDLENGTH | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| / RDATA / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr); if (ret < 0) { return -1; } if (_dns_left_len(context) < rr_len) { return -1; } memcpy(context->ptr, data_context.ptr, rr_len); context->ptr += rr_len; data_context.ptr += rr_len; return 0; } static int _dns_decode_raw(struct dns_context *context, unsigned char *raw, int len) { if (_dns_left_len(context) < len || len < 0) { return -1; } memcpy(raw, context->ptr, len); context->ptr += len; return 0; } static int _dns_decode_CNAME(struct dns_context *context, char *cname, int cname_size) { int ret = 0; ret = _dns_decode_domain(context, cname, cname_size); if (ret < 0) { return -1; } return 0; } static int _dns_encode_CNAME(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; char domain[DNS_MAX_CNAME_LEN]; int rr_len = 0; unsigned char *rr_len_ptr = NULL; struct dns_context data_context; _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } /* when code domain, len must plus 1, because of length at the beginning */ rr_len = 1; ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr); if (ret < 0) { return -1; } ret = _dns_encode_domain(context, (char *)data_context.ptr); if (ret < 0) { return -1; } rr_len += ret; data_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1; if (rr_len > rrs->len) { return -1; } _dns_write_short(&rr_len_ptr, ret); return 0; } static int _dns_decode_SRV(struct dns_context *context, unsigned short *priority, unsigned short *weight, unsigned short *port, char *target, int target_size) { int ret = 0; if (_dns_left_len(context) < 6) { return -1; } *priority = _dns_read_short(&context->ptr); *weight = _dns_read_short(&context->ptr); *port = _dns_read_short(&context->ptr); ret = _dns_decode_domain(context, target, target_size); if (ret < 0) { return -1; } return 0; } static int _dns_decode_SOA(struct dns_context *context, struct dns_soa *soa) { int ret = 0; ret = _dns_decode_domain(context, soa->mname, DNS_MAX_CNAME_LEN - 1); if (ret < 0) { return -1; } ret = _dns_decode_domain(context, soa->rname, DNS_MAX_CNAME_LEN - 1); if (ret < 0) { return -1; } if (_dns_left_len(context) < 20) { return -1; } soa->serial = _dns_read_int(&context->ptr); soa->refresh = _dns_read_int(&context->ptr); soa->retry = _dns_read_int(&context->ptr); soa->expire = _dns_read_int(&context->ptr); soa->minimum = _dns_read_int(&context->ptr); return 0; } static int _dns_encode_SOA(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; char domain[DNS_MAX_CNAME_LEN]; int rr_len = 0; unsigned char *rr_len_ptr = NULL; struct dns_context data_context; _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr); if (ret < 0) { return -1; } rr_len = 0; /* mname */ ret = _dns_encode_domain(context, (char *)data_context.ptr); if (ret < 0) { return -1; } rr_len += ret; data_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1; /* rname */ ret = _dns_encode_domain(context, (char *)data_context.ptr); if (ret < 0) { return -1; } rr_len += ret; data_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1; if (rr_len > rrs->len) { return -1; } rr_len += 20; _dns_write_short(&rr_len_ptr, rr_len); if (_dns_left_len(context) < 20) { return -1; } _dns_write_intptr(&context->ptr, data_context.ptr); data_context.ptr += 4; _dns_write_intptr(&context->ptr, data_context.ptr); data_context.ptr += 4; _dns_write_intptr(&context->ptr, data_context.ptr); data_context.ptr += 4; _dns_write_intptr(&context->ptr, data_context.ptr); data_context.ptr += 4; _dns_write_intptr(&context->ptr, data_context.ptr); data_context.ptr += 4; return 0; } static int _dns_encode_SRV(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; char domain[DNS_MAX_CNAME_LEN]; int rr_len = 0; unsigned char *rr_len_ptr = NULL; struct dns_context data_context; _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr); if (ret < 0) { return -1; } rr_len = 0; if (_dns_left_len(context) < 6) { return -1; } _dns_write_shortptr(&context->ptr, data_context.ptr); data_context.ptr += 2; _dns_write_shortptr(&context->ptr, data_context.ptr); data_context.ptr += 2; _dns_write_shortptr(&context->ptr, data_context.ptr); data_context.ptr += 2; rr_len += 6; ret = _dns_encode_domain(context, (char *)data_context.ptr); if (ret < 0) { return -1; } rr_len += ret; data_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1; _dns_write_short(&rr_len_ptr, rr_len); return 0; } static int _dns_decode_opt_ecs(struct dns_context *context, struct dns_opt_ecs *ecs, int opt_len) { int len = 0; if (opt_len < 4) { return -1; } ecs->family = _dns_read_short(&context->ptr); ecs->source_prefix = _dns_read_char(&context->ptr); ecs->scope_prefix = _dns_read_char(&context->ptr); len = (ecs->source_prefix / 8); len += (ecs->source_prefix % 8 > 0) ? 1 : 0; if (_dns_left_len(context) < len || len > (int)sizeof(ecs->addr)) { return -1; } memcpy(ecs->addr, context->ptr, len); context->ptr += len; tlog(TLOG_DEBUG, "ECS: family:%d, source_prefix:%d, scope_prefix:%d, len:%d", ecs->family, ecs->source_prefix, ecs->scope_prefix, len); tlog(TLOG_DEBUG, "%d.%d.%d.%d", ecs->addr[0], ecs->addr[1], ecs->addr[2], ecs->addr[3]); return 0; } static int _dns_decode_opt_cookie(struct dns_context *context, struct dns_opt_cookie *cookie, int opt_len) { if (opt_len < (int)member_size(struct dns_opt_cookie, client_cookie)) { return -1; } int len = 8; memcpy(cookie->client_cookie, context->ptr, len); context->ptr += len; opt_len -= len; if (opt_len <= 0) { cookie->server_cookie_len = 0; return 0; } if (opt_len < 8 || opt_len > (int)member_size(struct dns_opt_cookie, server_cookie)) { return -1; } memcpy(cookie->server_cookie, context->ptr, opt_len); cookie->server_cookie_len = opt_len; context->ptr += opt_len; tlog(TLOG_DEBUG, "OPT COOKIE"); return 0; } static int _dns_decode_opt_tcp_keepalive(struct dns_context *context, unsigned short *timeout, int opt_len) { if (opt_len == 0) { *timeout = 0; return 0; } if (opt_len < (int)sizeof(unsigned short)) { return -1; } *timeout = _dns_read_short(&context->ptr); tlog(TLOG_DEBUG, "OPT TCP KEEPALIVE %u", *timeout); return 0; } static int _dns_encode_OPT(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int opt_code = 0; int qclass = 0; char domain[DNS_MAX_CNAME_LEN]; struct dns_context data_context; int rr_len = 0; int ttl = 0; struct dns_opt *dns_opt = NULL; _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &opt_code, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } if (rr_len < (int)sizeof(*dns_opt)) { return -1; } if (_dns_left_len(context) < (rr_len)) { return -1; } dns_opt = (struct dns_opt *)data_context.ptr; _dns_write_short(&context->ptr, dns_opt->code); _dns_write_short(&context->ptr, dns_opt->length); if (_dns_left_len(context) < dns_opt->length) { return -1; } switch (dns_opt->code) { case DNS_OPT_T_ECS: { struct dns_opt_ecs *ecs = (struct dns_opt_ecs *)&(dns_opt->data); _dns_write_short(&context->ptr, ecs->family); _dns_write_char(&context->ptr, ecs->source_prefix); _dns_write_char(&context->ptr, ecs->scope_prefix); memcpy(context->ptr, ecs->addr, dns_opt->length - 4); context->ptr += dns_opt->length - 4; } break; default: memcpy(context->ptr, dns_opt->data, dns_opt->length); context->ptr += dns_opt->length; break; } return 0; } static int _dns_get_opts_data_len(struct dns_packet *packet, struct dns_rrs *rrs, int count) { int i = 0; int len = 0; int opt_code = 0; int qclass = 0; int ttl = 0; int ret = 0; char domain[DNS_MAX_CNAME_LEN]; struct dns_context data_context; int rr_len = 0; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &opt_code, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } len += rr_len; } return len; } static int _dns_encode_opts(struct dns_packet *packet, struct dns_context *context, struct dns_rrs *rrs, int count) { int i = 0; int len = 0; int ret = 0; unsigned int rcode = packet->opt_option; int rr_len = 0; int payloadsize = packet->payloadsize; unsigned char *rr_len_ptr = NULL; rr_len = _dns_get_opts_data_len(packet, rrs, count); if (rr_len < 0) { return -1; } if (payloadsize < DNS_DEFAULT_PACKET_SIZE) { payloadsize = DNS_DEFAULT_PACKET_SIZE; } ret = _dns_encode_rr_head(context, "", DNS_T_OPT, payloadsize, rcode, rr_len, &rr_len_ptr); if (ret < 0) { return -1; } if (_dns_left_len(context) < rr_len) { return -1; } for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_OPT(context, rrs); if (len < 0) { return -1; } } return 0; } static int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsigned int ttl, int rr_len) { unsigned short opt_code = 0; unsigned short opt_len = 0; unsigned short errcode = (ttl >> 16) & 0xFFFF; unsigned short ever = (ttl) & 0xFFFF; unsigned char *start = context->ptr; struct dns_packet *packet = context->packet; int ret = 0; UNUSED(ever); /* Field Name Field Type Description ------------------------------------------------------ NAME domain name empty (root domain) TYPE u_int16_t OPT CLASS u_int16_t sender's UDP payload size TTL u_int32_t extended RCODE and flags RDLEN u_int16_t describes RDATA RDATA octet stream {attribute,value} pairs +0 (MSB) +1 (LSB) +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0: | OPTION-CODE | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2: | OPTION-LENGTH | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4: | | / OPTION-DATA / / / +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ TTL +0 (MSB) +1 (LSB) +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0: | EXTENDED-RCODE | VERSION | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2: | Z | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ if (rr_len < 0) { tlog(TLOG_DEBUG, "opt len is invalid."); return -1; } if (errcode != 0) { tlog(TLOG_DEBUG, "extend rcode invalid, %d", errcode); return 0; } while (context->ptr - start < rr_len) { if (_dns_left_len(context) < 4) { tlog(TLOG_DEBUG, "data length is invalid, %d:%d", _dns_left_len(context), (int)(context->ptr - context->data)); return -1; } opt_code = _dns_read_short(&context->ptr); opt_len = _dns_read_short(&context->ptr); if (_dns_left_len(context) < opt_len) { tlog(TLOG_DEBUG, "read opt data failed, opt_code = %d, opt_len = %d", opt_code, opt_len); return -1; } tlog(TLOG_DEBUG, "opt type %d", opt_code); switch (opt_code) { case DNS_OPT_T_ECS: { struct dns_opt_ecs ecs; memset(&ecs, 0, sizeof(ecs)); ret = _dns_decode_opt_ecs(context, &ecs, opt_len); if (ret != 0) { tlog(TLOG_ERROR, "decode ecs failed."); return -1; } ret = dns_add_OPT_ECS(packet, &ecs); if (ret != 0) { tlog(TLOG_ERROR, "add ecs failed."); return -1; } } break; case DNS_OPT_T_COOKIE: { struct dns_opt_cookie cookie; ret = _dns_decode_opt_cookie(context, &cookie, opt_len); if (ret != 0) { tlog(TLOG_ERROR, "decode cookie failed."); return -1; } } break; case DNS_OPT_T_TCP_KEEPALIVE: { unsigned short timeout = 0; ret = _dns_decode_opt_tcp_keepalive(context, &timeout, opt_len); if (ret != 0) { tlog(TLOG_ERROR, "decode tcp keepalive failed."); return -1; } ret = dns_add_OPT_TCP_KEEPALIVE(packet, timeout); if (ret != 0) { tlog(TLOG_ERROR, "add tcp keepalive failed."); return -1; } } break; case DNS_OPT_T_PADDING: context->ptr += opt_len; break; default: context->ptr += opt_len; tlog(TLOG_DEBUG, "DNS opt type = %d not supported", opt_code); break; } } return 0; } static int _dns_encode_svcparam(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; char domain[DNS_MAX_CNAME_LEN]; char target[DNS_MAX_CNAME_LEN] = {0}; unsigned char *rr_len_ptr = NULL; unsigned char *start = NULL; unsigned char *rr_start = NULL; int ttl = 0; int priority = 0; struct dns_svcparam *param = NULL; ret = dns_svcparm_start(rrs, ¶m, domain, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN); if (ret < 0) { tlog(TLOG_DEBUG, "get https param failed."); return 0; } qtype = rrs->type; qclass = DNS_C_IN; ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, 0, &rr_len_ptr); if (ret < 0) { return -1; } rr_start = context->ptr; if (_dns_left_len(context) < 2) { tlog(TLOG_ERROR, "left len is invalid."); return -1; } _dns_write_short(&context->ptr, priority); ret = _dns_encode_domain(context, target); if (ret < 0) { return -1; } start = context->ptr; for (; param != NULL; param = dns_svcparm_next(rrs, param)) { if (context->ptr - start > rrs->len || _dns_left_len(context) <= 0) { return -1; } if (param->len + 4 > _dns_left_len(context)) { return -1; } _dns_write_short(&context->ptr, param->key); _dns_write_short(&context->ptr, param->len); switch (param->key) { case DNS_HTTPS_T_MANDATORY: case DNS_HTTPS_T_NO_DEFAULT_ALPN: case DNS_HTTPS_T_ALPN: case DNS_HTTPS_T_PORT: case DNS_HTTPS_T_IPV4HINT: case DNS_HTTPS_T_ECH: case DNS_HTTPS_T_IPV6HINT: { memcpy(context->ptr, param->value, param->len); context->ptr += param->len; } break; default: /* skip unknown key */ context->ptr -= 4; break; } } if (_dns_left_len(context) < 2) { return -1; } _dns_write_short(&rr_len_ptr, context->ptr - rr_start); return 0; } static int _dns_decode_SVCB_HTTPS(struct dns_context *context, const char *domain, dns_rr_type type, dns_type_t qtype, unsigned int ttl, int rr_len) { unsigned char *start = context->ptr; struct dns_packet *packet = context->packet; int ret = 0; unsigned short priority; unsigned short key; unsigned short value_len; unsigned char *value = NULL; char target[DNS_MAX_CNAME_LEN] = {0}; struct dns_rr_nested param; if (rr_len < 2) { tlog(TLOG_DEBUG, "https len is invalid."); return -1; } if (_dns_left_len(context) < rr_len) { tlog(TLOG_DEBUG, "https data length exceeds buffer."); return -1; } priority = _dns_read_short(&context->ptr); ret = _dns_decode_domain(context, target, sizeof(target)); if (ret < 0) { return -1; } /* Use unified function with qtype parameter */ _dns_add_SVCB_HTTPS_start(¶m, packet, DNS_RRS_AN, qtype, domain, ttl, priority, target); while (context->ptr - start < rr_len) { if (_dns_left_len(context) < 4) { tlog(TLOG_WARN, "data length is invalid, %d:%d", _dns_left_len(context), (int)(context->ptr - context->data)); return -1; } key = _dns_read_short(&context->ptr); value_len = _dns_read_short(&context->ptr); value = context->ptr; if (_dns_left_len(context) < value_len) { tlog(TLOG_ERROR, "read https data failed, svcParam key = %d, https_len = %d", key, value_len); return -1; } switch (key) { case DNS_HTTPS_T_MANDATORY: case DNS_HTTPS_T_ALPN: case DNS_HTTPS_T_NO_DEFAULT_ALPN: case DNS_HTTPS_T_PORT: case DNS_HTTPS_T_IPV4HINT: case DNS_HTTPS_T_ECH: case DNS_HTTPS_T_IPV6HINT: { dns_SVCB_add_raw(¶m, key, value, value_len); } break; default: tlog(TLOG_DEBUG, "DNS HTTPS key = %d not supported", key); break; } context->ptr += value_len; } /* Use unified end function */ dns_add_SVCB_end(¶m); return 0; } static int _dns_decode_qd(struct dns_context *context) { struct dns_packet *packet = context->packet; int len = 0; int qtype = 0; int qclass = 0; char domain[DNS_MAX_CNAME_LEN]; len = _dns_decode_qr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass); if (len < 0) { return -1; } len = dns_add_domain(packet, domain, qtype, qclass); if (len < 0) { return -1; } return 0; } static int _dns_decode_an(struct dns_context *context, dns_rr_type type) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; int rr_len = 0; char domain[DNS_MAX_CNAME_LEN]; struct dns_packet *packet = context->packet; unsigned char *start = NULL; /* decode rr head */ ret = _dns_decode_rr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len); if (ret < 0 || qclass < 0) { tlog(TLOG_DEBUG, "decode head failed."); return -1; } start = context->ptr; /* decode answer */ switch (qtype) { case DNS_T_A: { unsigned char addr[DNS_RR_A_LEN]; ret = _dns_decode_raw(context, addr, sizeof(addr)); if (ret < 0) { tlog(TLOG_DEBUG, "decode A failed, %s, len: %d:%d", domain, (int)(context->ptr - context->data), _dns_left_len(context)); return -1; } ret = dns_add_A(packet, type, domain, ttl, addr); if (ret < 0) { tlog(TLOG_DEBUG, "add A failed, %s", domain); return -1; } } break; case DNS_T_CNAME: { char cname[DNS_MAX_CNAME_LEN]; ret = _dns_decode_CNAME(context, cname, DNS_MAX_CNAME_LEN); if (ret < 0) { tlog(TLOG_DEBUG, "decode CNAME failed, %s, len: %d:%d", domain, (int)(context->ptr - context->data), _dns_left_len(context)); return -1; } ret = dns_add_CNAME(packet, type, domain, ttl, cname); if (ret < 0) { tlog(TLOG_DEBUG, "add CNAME failed, %s", domain); return -1; } } break; case DNS_T_SOA: { struct dns_soa soa; ret = _dns_decode_SOA(context, &soa); if (ret < 0) { tlog(TLOG_DEBUG, "decode SOA failed, %s", domain); return -1; } ret = dns_add_SOA(packet, type, domain, ttl, &soa); if (ret < 0) { tlog(TLOG_DEBUG, "add SOA failed, %s", domain); return -1; } } break; case DNS_T_NS: { char ns[DNS_MAX_CNAME_LEN]; ret = _dns_decode_CNAME(context, ns, DNS_MAX_CNAME_LEN); if (ret < 0) { tlog(TLOG_DEBUG, "decode NS failed, %s", domain); return -1; } ret = dns_add_NS(packet, type, domain, ttl, ns); if (ret < 0) { tlog(TLOG_DEBUG, "add NS failed, %s", domain); return -1; } } break; case DNS_T_PTR: { char name[DNS_MAX_CNAME_LEN]; ret = _dns_decode_CNAME(context, name, DNS_MAX_CNAME_LEN); if (ret < 0) { tlog(TLOG_DEBUG, "decode PTR failed, %s", domain); return -1; } ret = dns_add_PTR(packet, type, domain, ttl, name); if (ret < 0) { tlog(TLOG_DEBUG, "add PTR failed, %s", domain); return -1; } } break; case DNS_T_AAAA: { unsigned char addr[DNS_RR_AAAA_LEN]; ret = _dns_decode_raw(context, addr, sizeof(addr)); if (ret < 0) { tlog(TLOG_DEBUG, "decode AAAA failed, %s", domain); return -1; } ret = dns_add_AAAA(packet, type, domain, ttl, addr); if (ret < 0) { tlog(TLOG_DEBUG, "add AAAA failed, %s", domain); return -1; } } break; case DNS_T_SRV: { unsigned short priority = 0; unsigned short weight = 0; unsigned short port = 0; char target[DNS_MAX_CNAME_LEN]; ret = _dns_decode_SRV(context, &priority, &weight, &port, target, DNS_MAX_CNAME_LEN); if (ret < 0) { tlog(TLOG_DEBUG, "decode SRV failed, %s", domain); return -1; } ret = dns_add_SRV(packet, type, domain, ttl, priority, weight, port, target); if (ret < 0) { tlog(TLOG_DEBUG, "add SRV failed, %s", domain); return -1; } } break; case DNS_T_OPT: { unsigned char *opt_start = context->ptr; ret = _dns_decode_opt(context, type, ttl, rr_len); if (ret < 0) { tlog(TLOG_DEBUG, "decode opt failed, %s", domain); return -1; } if (context->ptr - opt_start != rr_len) { tlog(TLOG_DEBUG, "opt length mismatch, %s\n", domain); return -1; } dns_set_OPT_option(packet, ttl); dns_set_OPT_payload_size(packet, qclass); } break; case DNS_T_HTTPS: case DNS_T_SVCB: { unsigned char *https_start = context->ptr; ret = _dns_decode_SVCB_HTTPS(context, domain, type, qtype, ttl, rr_len); if (ret < 0) { tlog(TLOG_DEBUG, "decode HTTPS failed, %s", domain); return -1; } if (context->ptr - https_start != rr_len) { tlog(TLOG_DEBUG, "opt length mismatch, %s\n", domain); return -1; } } break; default: { unsigned char raw_data[1024]; if (_dns_left_len(context) < rr_len || rr_len >= (int)sizeof(raw_data)) { tlog(TLOG_DEBUG, "length mismatch\n"); return -1; } ret = _dns_decode_raw(context, raw_data, rr_len); if (ret < 0) { tlog(TLOG_DEBUG, "decode A failed, %s", domain); return -1; } ret = _dns_add_RAW(packet, type, qtype, domain, ttl, raw_data, rr_len); if (ret < 0) { tlog(TLOG_DEBUG, "add raw failed, %s", domain); return -1; } tlog(TLOG_DEBUG, "DNS type = %d not supported", qtype); break; } } if (context->ptr - start != rr_len) { tlog(TLOG_DEBUG, "length mismatch, %s, %ld:%d", domain, (long)(context->ptr - start), rr_len); return -1; } return 0; } static int _dns_encode_qd(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; char domain[DNS_MAX_CNAME_LEN]; struct dns_context data_context; _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_qr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass); if (ret < 0) { return -1; } ret = _dns_encode_qr_head(context, domain, qtype, qclass); if (ret < 0) { return -1; } if (domain[0] == '-') { /* for google and cloudflare */ unsigned char *ptr = context->ptr - 7; memcpy(ptr, "\xC0\x12", 2); ptr += 2; _dns_write_short(&ptr, qtype); _dns_write_short(&ptr, qclass); context->ptr = ptr; } return 0; } static int _dns_encode_an(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; switch (rrs->type) { case DNS_T_A: case DNS_T_AAAA: { ret = _dns_encode_raw(context, rrs); if (ret < 0) { return -1; } } break; case DNS_T_CNAME: case DNS_T_PTR: ret = _dns_encode_CNAME(context, rrs); if (ret < 0) { return -1; } break; case DNS_T_SOA: ret = _dns_encode_SOA(context, rrs); if (ret < 0) { return -1; } break; case DNS_T_SRV: ret = _dns_encode_SRV(context, rrs); if (ret < 0) { return -1; } break; case DNS_T_HTTPS: case DNS_T_SVCB: ret = _dns_encode_svcparam(context, rrs); if (ret < 0) { return -1; } break; default: ret = _dns_encode_raw(context, rrs); if (ret < 0) { return -1; } break; } return 0; } static int _dns_decode_body(struct dns_context *context) { struct dns_packet *packet = context->packet; struct dns_head *head = &packet->head; int i = 0; int ret = 0; int count = 0; count = head->qdcount; head->qdcount = 0; for (i = 0; i < count; i++) { ret = _dns_decode_qd(context); if (ret < 0) { tlog(TLOG_DEBUG, "decode qd failed."); return -1; } } count = head->ancount; head->ancount = 0; for (i = 0; i < count; i++) { ret = _dns_decode_an(context, DNS_RRS_AN); if (ret < 0) { tlog(TLOG_DEBUG, "decode an failed."); return -1; } } count = head->nscount; head->nscount = 0; for (i = 0; i < count; i++) { ret = _dns_decode_an(context, DNS_RRS_NS); if (ret < 0) { tlog(TLOG_DEBUG, "decode ns failed."); return -1; } } count = head->nrcount; head->nrcount = 0; for (i = 0; i < count; i++) { ret = _dns_decode_an(context, DNS_RRS_NR); if (ret < 0) { tlog(TLOG_DEBUG, "decode nr failed."); return -1; } } return 0; } static int _dns_encode_body(struct dns_context *context) { struct dns_packet *packet = context->packet; struct dns_head *head = &packet->head; int i = 0; int len = 0; struct dns_rrs *rrs = NULL; int count = 0; rrs = dns_get_rrs_start(packet, DNS_RRS_QD, &count); head->qdcount = count; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_qd(context, rrs); if (len < 0) { return -1; } } rrs = dns_get_rrs_start(packet, DNS_RRS_AN, &count); head->ancount = count; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_an(context, rrs); if (len < 0) { return -1; } } rrs = dns_get_rrs_start(packet, DNS_RRS_NS, &count); head->nscount = count; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_an(context, rrs); if (len < 0) { return -1; } } rrs = dns_get_rrs_start(packet, DNS_RRS_NR, &count); head->nrcount = count; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_an(context, rrs); if (len < 0) { return -1; } } rrs = dns_get_rrs_start(packet, DNS_RRS_OPT, &count); if (count > 0 || packet->payloadsize > 0) { len = _dns_encode_opts(packet, context, rrs, count); if (len < 0) { return -1; } head->nrcount++; } return 0; } int dns_packet_init(struct dns_packet *packet, int size, struct dns_head *head) { struct dns_head *init_head = &packet->head; if (size < (int)sizeof(*packet)) { return -1; } memset(packet, 0, size); packet->size = size; init_head->id = head->id; init_head->qr = head->qr; init_head->opcode = head->opcode; init_head->aa = head->aa; init_head->tc = head->tc; init_head->rd = head->rd; init_head->ra = head->ra; init_head->z = head->z; init_head->ad = head->ad; init_head->cd = head->cd; init_head->rcode = head->rcode; packet->questions = DNS_RR_END; packet->answers = DNS_RR_END; packet->nameservers = DNS_RR_END; packet->additional = DNS_RR_END; packet->optional = DNS_RR_END; packet->optcount = 0; packet->payloadsize = 0; return 0; } int dns_decode_head_only(struct dns_packet *packet, int maxsize, unsigned char *data, int size) { struct dns_head *head = &packet->head; struct dns_context context; int ret = 0; memset(&context, 0, sizeof(context)); memset(packet, 0, sizeof(*packet)); context.data = data; context.packet = packet; context.ptr = data; context.maxsize = size; context.namedict = &packet->namedict; ret = dns_packet_init(packet, maxsize, head); if (ret != 0) { return -1; } ret = _dns_decode_head(&context); if (ret < 0) { return -1; } packet->size = context.ptr - context.data + sizeof(*packet); return 0; } int dns_decode(struct dns_packet *packet, int maxsize, unsigned char *data, int size) { struct dns_head *head = &packet->head; struct dns_context context; int ret = 0; memset(&context, 0, sizeof(context)); memset(packet, 0, sizeof(*packet)); context.data = data; context.packet = packet; context.ptr = data; context.maxsize = size; context.namedict = &packet->namedict; ret = dns_packet_init(packet, maxsize, head); if (ret != 0) { return -1; } ret = _dns_decode_head(&context); if (ret < 0) { return -1; } ret = _dns_decode_body(&context); if (ret < 0) { tlog(TLOG_DEBUG, "decode body failed.\n"); return -1; } packet->size = context.ptr - context.data + sizeof(*packet); return 0; } int dns_encode(unsigned char *data, int size, struct dns_packet *packet) { int ret = 0; struct dns_context context; struct dns_packet_dict namedict; memset(&context, 0, sizeof(context)); memset(&namedict, 0, sizeof(namedict)); context.data = data; context.packet = packet; context.ptr = data; context.maxsize = size; context.namedict = &namedict; ret = _dns_encode_head(&context); if (ret < 0) { return -1; } ret = _dns_encode_body(&context); if (ret < 0) { return -1; } ret = _dns_encode_head_count(&context); if (ret < 0) { return -1; } return context.ptr - context.data; } static int _dns_update_domain(struct dns_context *context, const char *domain) { int len = 0; int ptr_jump = 0; size_t output_len = 0; unsigned char *ptr = context->ptr; unsigned char *packet = context->data; int packet_size = context->maxsize; size_t domain_len = strlen(domain); size_t processed_len = 0; while (1) { if (ptr >= packet + packet_size || ptr < packet || ptr_jump > 32 || processed_len > domain_len + 1) { return -1; } len = *ptr; if (len == 0) { ptr++; processed_len++; break; } /* compressed domain */ if (len >= 0xC0) { if ((ptr + 2) > (packet + packet_size)) { return -1; } /* read offset */ len = _dns_read_short(&ptr) & 0x3FFF; ptr = packet + len; if (ptr > packet + packet_size) { return -1; } ptr_jump++; continue; } ptr_jump = 0; if (output_len > 0) { output_len += 1; } if (ptr > packet + packet_size) { return -1; } ptr++; /* update domain */ memcpy(ptr, domain + processed_len, len); ptr += len; processed_len += len + 1; output_len += len; } if (output_len != domain_len) { tlog(TLOG_DEBUG, "update domain failed, length mismatch. output_len: %zu, domain_len: %zu", output_len, domain_len); return -1; } return 0; } static int _dns_update_rr_domain(struct dns_context *context, unsigned char *rr_start, const char *domain, struct dns_update_param *param) { const char *query_domain = param->query_domain; unsigned char *old_context_ptr = context->ptr; if (param->query_domain == NULL) { return 0; } if (strncasecmp(domain, query_domain, DNS_MAX_CNAME_LEN) != 0) { return 0; } context->ptr = rr_start; if (_dns_update_domain(context, query_domain) != 0) { tlog(TLOG_DEBUG, "update domain failed, %s", domain); context->ptr = old_context_ptr; return -1; } context->ptr = old_context_ptr; return 0; } static int _dns_update_qd(struct dns_context *context, dns_rr_type type, struct dns_update_param *param) { char domain[DNS_MAX_CNAME_LEN]; int qtype = 0; int qclass = 0; int len = 0; unsigned char *rr_start = context->ptr; len = _dns_decode_qr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass); if (len < 0) { tlog(TLOG_DEBUG, "update qd failed."); return -1; } int ret = _dns_update_rr_domain(context, rr_start, domain, param); if (ret < 0) { tlog(TLOG_DEBUG, "domain not match, %s", domain); return -1; } return 0; } static int _dns_update_an(struct dns_context *context, dns_rr_type type, struct dns_update_param *param) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; int rr_len = 0; char domain[DNS_MAX_CNAME_LEN]; unsigned char *start = NULL; unsigned char *rr_start = context->ptr; /* decode rr head */ ret = _dns_decode_rr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len); if (ret < 0) { tlog(TLOG_DEBUG, "decode head failed."); return -1; } ret = _dns_update_rr_domain(context, rr_start, domain, param); if (ret < 0) { tlog(TLOG_DEBUG, "domain not match, %s", domain); return -1; } start = context->ptr; switch (qtype) { case DNS_T_OPT: break; default: { unsigned char *ttl_ptr = start - sizeof(int) - sizeof(short); if (param->ip_ttl < 0) { break; } _dns_write_int(&ttl_ptr, param->ip_ttl); } break; } context->ptr += rr_len; if (context->ptr - start != rr_len) { tlog(TLOG_ERROR, "length mismatch , %s, %ld:%d", domain, (long)(context->ptr - start), rr_len); return -1; } return 0; } static int _dns_update_body(struct dns_context *context, struct dns_update_param *param) { struct dns_packet *packet = context->packet; struct dns_head *head = &packet->head; int i = 0; int ret = 0; int count = 0; count = head->qdcount; head->qdcount = 0; for (i = 0; i < count; i++) { ret = _dns_update_qd(context, DNS_RRS_QD, param); if (ret < 0) { tlog(TLOG_DEBUG, "update qd failed."); return -1; } } count = head->ancount; head->ancount = 0; for (i = 0; i < count; i++) { ret = _dns_update_an(context, DNS_RRS_AN, param); if (ret < 0) { tlog(TLOG_DEBUG, "update an failed."); return -1; } } count = head->nscount; head->nscount = 0; for (i = 0; i < count; i++) { ret = _dns_update_an(context, DNS_RRS_NS, param); if (ret < 0) { tlog(TLOG_DEBUG, "update ns failed."); return -1; } } count = head->nrcount; head->nrcount = 0; for (i = 0; i < count; i++) { ret = _dns_update_an(context, DNS_RRS_NR, param); if (ret < 0) { tlog(TLOG_DEBUG, "update nr failed."); return -1; } } return 0; } static int _dns_update_id(unsigned char *data, int size, struct dns_update_param *param) { unsigned char *ptr = data; _dns_write_short(&ptr, param->id); return 0; } int dns_packet_update(unsigned char *data, int size, struct dns_update_param *param) { struct dns_packet packet; int maxsize = sizeof(packet); struct dns_head *head = &packet.head; struct dns_context context; int ret = 0; memset(&context, 0, sizeof(context)); memset(&packet, 0, sizeof(packet)); context.data = data; context.packet = &packet; context.ptr = data; context.maxsize = size; ret = dns_packet_init(&packet, maxsize, head); if (ret != 0) { return -1; } ret = _dns_decode_head(&context); if (ret < 0) { return -1; } ret = _dns_update_id(data, size, param); if (ret < 0) { return -1; } ret = _dns_update_body(&context, param); if (ret < 0) { tlog(TLOG_DEBUG, "decode body failed.\n"); return -1; } return 0; } ================================================ FILE: src/dns_cache.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/dns_cache.h" #include "smartdns/dns_stats.h" #include "smartdns/lib/stringutil.h" #include "smartdns/timer.h" #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #include #include #include #include #define DNS_CACHE_MAX_HITNUM 6000 #define DNS_CACHE_HITNUM_STEP 3 #define DNS_CACHE_HITNUM_STEP_MAX 6 #define DNS_CACHE_READ_TIMEOUT 60 #define DNS_CACHE_FAIL_TIMEOUT (60 * 5) #define EXPIRED_DOMAIN_PREFETCH_TIME (3600 * 8) struct dns_cache_head { struct hash_table cache_hash; struct list_head cache_list; atomic_t num; atomic_t mem_size; int size; long max_mem_size; pthread_mutex_t lock; dns_cache_callback timeout_callback; }; typedef int (*dns_cache_read_callback)(struct dns_cache_record *cache_record, struct dns_cache_data *cache_data); static int is_cache_init; static struct dns_cache_head dns_cache_head; int dns_cache_init(int size, int mem_size, dns_cache_callback timeout_callback) { int bits = 0; pthread_mutexattr_t mta; if (is_cache_init == 1) { return -1; } INIT_LIST_HEAD(&dns_cache_head.cache_list); bits = ilog2(size) - 1; if (bits >= 20) { bits = 20; } else if (bits < 12) { bits = 12; } hash_table_init(dns_cache_head.cache_hash, bits); atomic_set(&dns_cache_head.num, 0); atomic_set(&dns_cache_head.mem_size, 0); dns_cache_head.size = size; dns_cache_head.max_mem_size = mem_size; if (mem_size > 0) { dns_cache_head.size = INT32_MAX; } dns_cache_head.timeout_callback = timeout_callback; pthread_mutexattr_init(&mta); pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&dns_cache_head.lock, &mta); pthread_mutexattr_destroy(&mta); is_cache_init = 1; return 0; } static struct dns_cache *_dns_cache_first(void) { return list_first_entry_or_null(&dns_cache_head.cache_list, struct dns_cache, list); } static void _dns_cache_delete(struct dns_cache *dns_cache) { pthread_mutex_lock(&dns_cache_head.lock); hash_del(&dns_cache->node); list_del_init(&dns_cache->list); if (dns_timer_del(&dns_cache->timer)) { tlog(TLOG_DEBUG, "dns cache timer is still pending when delete dns cache."); } pthread_mutex_unlock(&dns_cache_head.lock); atomic_dec(&dns_cache_head.num); atomic_sub(sizeof(*dns_cache), &dns_cache_head.mem_size); if (dns_cache->cache_data) { dns_cache_data_put(dns_cache->cache_data); } dns_cache->cache_data = NULL; free(dns_cache); } void dns_cache_get(struct dns_cache *dns_cache) { if (atomic_inc_return(&dns_cache->ref) == 1) { BUG("BUG: dns_cache is invalid."); return; } } void dns_cache_release(struct dns_cache *dns_cache) { int refcnt = 0; if (dns_cache == NULL) { return; } refcnt = atomic_dec_return(&dns_cache->ref); if (refcnt > 0) { return; } else if (refcnt < 0) { BUG("dns_cache refcnt is invalid: %d", refcnt); return; } _dns_cache_delete(dns_cache); } static void _dns_cache_remove(struct dns_cache *dns_cache) { /* * If the list is empty, it means the cache has been removed by other threads. * The reference count has been decreased by the other thread. * We should simply return to avoid double free. */ if (list_empty(&dns_cache->list)) { return; } hash_del(&dns_cache->node); list_del_init(&dns_cache->list); if (dns_timer_del(&dns_cache->timer)) { dns_cache_release(dns_cache); } dns_cache_release(dns_cache); } uint32_t dns_cache_get_query_flag(struct dns_cache *dns_cache) { return dns_cache->info.query_flag; } const char *dns_cache_get_dns_group_name(struct dns_cache *dns_cache) { return dns_cache->info.dns_group_name; } struct dns_cache_data *dns_cache_new_data_packet(void *packet, size_t packet_len) { struct dns_cache_packet *cache_packet = NULL; size_t data_size = 0; if (packet == NULL || packet_len <= 0) { return NULL; } data_size = sizeof(*cache_packet) + packet_len; cache_packet = zalloc(1, data_size); if (cache_packet == NULL) { return NULL; } memcpy(cache_packet->data, packet, packet_len); cache_packet->head.size = packet_len; cache_packet->head.magic = MAGIC_CACHE_DATA; atomic_set(&cache_packet->head.ref, 1); atomic_add(data_size, &dns_cache_head.mem_size); return (struct dns_cache_data *)cache_packet; } static void dns_cache_expired(struct tw_base *base, struct tw_timer_list *timer, void *data, unsigned long timestamp) { struct dns_cache *dns_cache = data; int mod_ret = 0; if (dns_cache_head.timeout_callback) { dns_cache_tmout_action_t tmout_act = dns_cache_head.timeout_callback(dns_cache); switch (tmout_act) { case DNS_CACHE_TMOUT_ACTION_OK: break; case DNS_CACHE_TMOUT_ACTION_UPDATE: mod_ret = dns_timer_mod(&dns_cache->timer, dns_cache->info.timeout); goto out; case DNS_CACHE_TMOUT_ACTION_DEL: dns_cache_delete(dns_cache); goto out; case DNS_CACHE_TMOUT_ACTION_RETRY: mod_ret = dns_timer_mod(&dns_cache->timer, DNS_CACHE_FAIL_TIMEOUT); goto out; default: break; } } dns_cache_release(dns_cache); return; out: if (mod_ret == 0) { dns_cache_release(dns_cache); } } static struct dns_cache *_dns_cache_lookup(struct dns_cache_key *cache_key) { uint32_t key = 0; struct dns_cache *dns_cache = NULL; struct dns_cache *dns_cache_ret = NULL; time_t now = 0; key = hash_string_case(cache_key->domain); key = jhash(&cache_key->qtype, sizeof(cache_key->qtype), key); key = hash_string_initval(cache_key->dns_group_name, key); key = jhash(&cache_key->query_flag, sizeof(cache_key->query_flag), key); time(&now); /* find cache */ pthread_mutex_lock(&dns_cache_head.lock); hash_table_for_each_possible(dns_cache_head.cache_hash, dns_cache, node, key) { if (dns_cache->info.qtype != cache_key->qtype) { continue; } if (strncasecmp(cache_key->domain, dns_cache->info.domain, DNS_MAX_CNAME_LEN) != 0) { continue; } if (strncmp(cache_key->dns_group_name, dns_cache->info.dns_group_name, DNS_GROUP_NAME_LEN) != 0) { continue; } if (cache_key->query_flag != dns_cache->info.query_flag) { continue; } dns_cache_ret = dns_cache; break; } if (dns_cache_ret) { dns_cache_get(dns_cache_ret); } pthread_mutex_unlock(&dns_cache_head.lock); return dns_cache_ret; } struct dns_cache *dns_cache_lookup(struct dns_cache_key *cache_key) { struct dns_cache *dns_cache_ret = NULL; if (dns_cache_head.size <= 0) { return NULL; } stats_inc(&dns_stats.cache.check_count); dns_cache_ret = _dns_cache_lookup(cache_key); if (dns_cache_ret) { stats_inc(&dns_stats.cache.hit_count); } return dns_cache_ret; } static int _dns_cache_replace(struct dns_cache_key *cache_key, int rcode, int ttl, int speed, int timeout, int update_time, struct dns_cache_data *cache_data) { struct dns_cache *dns_cache = NULL; struct dns_cache_data *old_cache_data = NULL; if (dns_cache_head.size <= 0) { return 0; } /* lookup existing cache */ dns_cache = _dns_cache_lookup(cache_key); if (dns_cache == NULL) { return -1; } if (ttl < DNS_CACHE_TTL_MIN) { ttl = DNS_CACHE_TTL_MIN; } /* update cache data */ pthread_mutex_lock(&dns_cache_head.lock); dns_cache->info.rcode = rcode; dns_cache->info.qtype = cache_key->qtype; dns_cache->info.query_flag = cache_key->query_flag; dns_cache->info.ttl = ttl; dns_cache->info.speed = speed; dns_cache->info.timeout = timeout; dns_cache->info.is_visited = 1; if (cache_data) { old_cache_data = dns_cache->cache_data; dns_cache->cache_data = cache_data; } if (update_time) { time(&dns_cache->info.insert_time); } time(&dns_cache->info.replace_time); list_del(&dns_cache->list); list_add_tail(&dns_cache->list, &dns_cache_head.cache_list); if (dns_timer_mod(&dns_cache->timer, timeout) == 0) { dns_cache_get(dns_cache); dns_cache->timer.expires = timeout; dns_timer_add(&dns_cache->timer); } pthread_mutex_unlock(&dns_cache_head.lock); if (old_cache_data) { dns_cache_data_put(old_cache_data); } dns_cache_release(dns_cache); return 0; } int dns_cache_replace(struct dns_cache_key *cache_key, int rcode, int ttl, int speed, int timeout, int update_time, struct dns_cache_data *cache_data) { return _dns_cache_replace(cache_key, rcode, ttl, speed, timeout, update_time, cache_data); } static void _dns_cache_remove_by_domain(struct dns_cache_key *cache_key) { uint32_t key = 0; struct dns_cache *dns_cache = NULL; key = hash_string_case(cache_key->domain); key = jhash(&cache_key->qtype, sizeof(cache_key->qtype), key); key = hash_string_initval(cache_key->dns_group_name, key); key = jhash(&cache_key->query_flag, sizeof(cache_key->query_flag), key); pthread_mutex_lock(&dns_cache_head.lock); hash_table_for_each_possible(dns_cache_head.cache_hash, dns_cache, node, key) { if (dns_cache->info.qtype != cache_key->qtype) { continue; } if (dns_cache->info.query_flag != cache_key->query_flag) { continue; } if (strncasecmp(cache_key->domain, dns_cache->info.domain, DNS_MAX_CNAME_LEN) != 0) { continue; } if (strncmp(dns_cache->info.dns_group_name, cache_key->dns_group_name, DNS_GROUP_NAME_LEN) != 0) { continue; } _dns_cache_remove(dns_cache); break; } pthread_mutex_unlock(&dns_cache_head.lock); } static int _dns_cache_insert(struct dns_cache_info *info, struct dns_cache_data *cache_data, struct list_head *head, int timeout) { uint32_t key = 0; struct dns_cache *dns_cache = NULL; int loop_count = 0; if (cache_data == NULL || info == NULL) { goto errout; } /* if cache already exists, free */ struct dns_cache_key cache_key; cache_key.qtype = info->qtype; cache_key.query_flag = info->query_flag; cache_key.domain = info->domain; cache_key.dns_group_name = info->dns_group_name; _dns_cache_remove_by_domain(&cache_key); dns_cache = zalloc(1, sizeof(*dns_cache)); if (dns_cache == NULL) { goto errout; } key = hash_string_case(info->domain); key = jhash(&info->qtype, sizeof(info->qtype), key); key = hash_string_initval(info->dns_group_name, key); key = jhash(&info->query_flag, sizeof(info->query_flag), key); atomic_set(&dns_cache->ref, 1); memcpy(&dns_cache->info, info, sizeof(*info)); dns_cache->cache_data = cache_data; dns_cache->timer.function = dns_cache_expired; dns_cache->timer.expires = timeout; dns_cache->timer.data = dns_cache; INIT_LIST_HEAD(&dns_cache->check_list); pthread_mutex_lock(&dns_cache_head.lock); atomic_inc(&dns_cache_head.num); /* Release extra cache, remove oldest cache record */ do { int need_remove = 0; if (dns_cache_head.max_mem_size > 0 && atomic_read(&dns_cache_head.mem_size) > dns_cache_head.max_mem_size) { need_remove = 1; } if (atomic_read(&dns_cache_head.num) > dns_cache_head.size) { need_remove = 1; } if (need_remove == 0) { break; } struct dns_cache *del_cache = _dns_cache_first(); if (del_cache == NULL) { break; } _dns_cache_remove(del_cache); } while (loop_count++ < 32); hash_table_add(dns_cache_head.cache_hash, &dns_cache->node, key); list_add_tail(&dns_cache->list, head); atomic_add(sizeof(*dns_cache), &dns_cache_head.mem_size); dns_cache_get(dns_cache); dns_timer_add(&dns_cache->timer); pthread_mutex_unlock(&dns_cache_head.lock); return 0; errout: if (dns_cache) { dns_cache_release(dns_cache); } return -1; } int dns_cache_insert(struct dns_cache_key *cache_key, int rcode, int ttl, int speed, int timeout, struct dns_cache_data *cache_data) { struct dns_cache_info info; if (cache_data == NULL || cache_key == NULL || cache_key->dns_group_name == NULL || cache_key->domain == NULL) { return -1; } if (dns_cache_head.size <= 0) { dns_cache_data_put(cache_data); return 0; } if (ttl < DNS_CACHE_TTL_MIN) { ttl = DNS_CACHE_TTL_MIN; } memset(&info, 0, sizeof(info)); info.hitnum = 3; safe_strncpy(info.domain, cache_key->domain, DNS_MAX_CNAME_LEN); info.qtype = cache_key->qtype; safe_strncpy(info.dns_group_name, cache_key->dns_group_name, DNS_GROUP_NAME_LEN); info.query_flag = cache_key->query_flag; info.ttl = ttl; info.hitnum_update_add = DNS_CACHE_HITNUM_STEP; info.speed = speed; info.timeout = timeout; info.is_visited = 1; info.rcode = rcode; time(&info.insert_time); time(&info.replace_time); return _dns_cache_insert(&info, cache_data, &dns_cache_head.cache_list, timeout); } int dns_cache_update_timer(struct dns_cache_key *key, int timeout) { struct dns_cache *dns_cache = _dns_cache_lookup(key); if (dns_cache == NULL) { return -1; } pthread_mutex_lock(&dns_cache_head.lock); if (dns_timer_mod(&dns_cache->timer, timeout) == 0) { dns_cache_get(dns_cache); dns_cache->timer.expires = timeout; dns_timer_add(&dns_cache->timer); } pthread_mutex_unlock(&dns_cache_head.lock); dns_cache_release(dns_cache); return 0; } int dns_cache_get_ttl(struct dns_cache *dns_cache) { time_t now = 0; int ttl = 0; time(&now); ttl = dns_cache->info.insert_time + dns_cache->info.ttl - now; if (ttl < 0) { return 0; } return ttl; } struct dns_cache_data *dns_cache_get_data(struct dns_cache *dns_cache) { struct dns_cache_data *cache_data; pthread_mutex_lock(&dns_cache_head.lock); if (dns_cache->cache_data == NULL) { pthread_mutex_unlock(&dns_cache_head.lock); return NULL; } dns_cache_data_get(dns_cache->cache_data); cache_data = dns_cache->cache_data; pthread_mutex_unlock(&dns_cache_head.lock); return cache_data; } void dns_cache_data_get(struct dns_cache_data *cache_data) { if (atomic_inc_return(&cache_data->head.ref) == 1) { tlog(TLOG_ERROR, "BUG: dns_cache data is invalid."); return; } return; } void dns_cache_flush(void) { struct dns_cache *dns_cache = NULL; struct dns_cache *tmp = NULL; pthread_mutex_lock(&dns_cache_head.lock); list_for_each_entry_safe(dns_cache, tmp, &dns_cache_head.cache_list, list) { _dns_cache_remove(dns_cache); } pthread_mutex_unlock(&dns_cache_head.lock); } void dns_cache_data_put(struct dns_cache_data *cache_data) { if (cache_data == NULL) { return; } if (!atomic_dec_and_test(&cache_data->head.ref)) { return; } atomic_sub(cache_data->head.size + sizeof(*cache_data), &dns_cache_head.mem_size); free(cache_data); } int dns_cache_is_visited(struct dns_cache *dns_cache) { return dns_cache->info.is_visited; } int dns_cache_total_num(void) { return atomic_read(&dns_cache_head.num); } long dns_cache_total_memsize(void) { return atomic_read(&dns_cache_head.mem_size); } void dns_cache_delete(struct dns_cache *dns_cache) { pthread_mutex_lock(&dns_cache_head.lock); _dns_cache_remove(dns_cache); pthread_mutex_unlock(&dns_cache_head.lock); } int dns_cache_hitnum_dec_get(struct dns_cache *dns_cache) { pthread_mutex_lock(&dns_cache_head.lock); dns_cache->info.hitnum--; if (dns_cache->info.hitnum_update_add > DNS_CACHE_HITNUM_STEP) { dns_cache->info.hitnum_update_add--; } pthread_mutex_unlock(&dns_cache_head.lock); return dns_cache->info.hitnum; } void dns_cache_update(struct dns_cache *dns_cache) { pthread_mutex_lock(&dns_cache_head.lock); if (!list_empty(&dns_cache->list)) { list_del_init(&dns_cache->list); list_add_tail(&dns_cache->list, &dns_cache_head.cache_list); dns_cache->info.hitnum += dns_cache->info.hitnum_update_add; if (dns_cache->info.hitnum > DNS_CACHE_MAX_HITNUM) { dns_cache->info.hitnum = DNS_CACHE_MAX_HITNUM; } if (dns_cache->info.hitnum_update_add < DNS_CACHE_HITNUM_STEP_MAX) { dns_cache->info.hitnum_update_add++; } dns_cache->info.is_visited = 1; } pthread_mutex_unlock(&dns_cache_head.lock); } static int _dns_cache_read_to_cache(struct dns_cache_record *cache_record, struct dns_cache_data *cache_data) { struct list_head *head = NULL; head = &dns_cache_head.cache_list; struct dns_cache_info *info = &cache_record->info; int expired_time = 0; time_t now = time(NULL); if (now < info->replace_time) { info->replace_time = now; } int passed_time = now - info->replace_time; int timeout = info->timeout - passed_time; struct dns_conf_group *rule_group = dns_server_get_rule_group(info->dns_group_name); if (rule_group->dns_prefetch) { if (rule_group->dns_serve_expired) { expired_time = rule_group->dns_serve_expired_prefetch_time; if (expired_time == 0) { expired_time = rule_group->dns_serve_expired_ttl / 2; if (expired_time == 0 || expired_time > EXPIRED_DOMAIN_PREFETCH_TIME) { expired_time = EXPIRED_DOMAIN_PREFETCH_TIME; } } } else { timeout -= 3; } } if ((timeout > expired_time + info->ttl) && expired_time > 0) { timeout = expired_time + info->ttl; } if (timeout < DNS_CACHE_READ_TIMEOUT * 2) { timeout = DNS_CACHE_READ_TIMEOUT + (rand() % DNS_CACHE_READ_TIMEOUT); } dns_cache_data_get(cache_data); if (_dns_cache_insert(&cache_record->info, cache_data, head, timeout) != 0) { tlog(TLOG_ERROR, "insert cache data failed."); dns_cache_data_put(cache_data); cache_data = NULL; goto errout; } daemon_keepalive(); return 0; errout: return -1; } static int _dns_cache_read_record(int fd, uint32_t cache_number, dns_cache_read_callback callback) { unsigned int i = 0; ssize_t ret = 0; int data_size = 0; struct dns_cache_record cache_record; struct dns_cache_data_head data_head; struct dns_cache_data *cache_data = NULL; for (i = 0; i < cache_number; i++) { ret = read(fd, &cache_record, sizeof(cache_record)); if (ret != sizeof(cache_record)) { tlog(TLOG_ERROR, "read cache failed, %s", strerror(errno)); goto errout; } if (cache_record.magic != MAGIC_RECORD) { tlog(TLOG_ERROR, "magic is invalid."); goto errout; } ret = read(fd, &data_head, sizeof(data_head)); if (ret != sizeof(data_head)) { tlog(TLOG_ERROR, "read data head failed, %s", strerror(errno)); goto errout; } if (data_head.magic != MAGIC_CACHE_DATA) { tlog(TLOG_ERROR, "data magic is invalid."); goto errout; } if (data_head.size > 1024 * 8) { tlog(TLOG_ERROR, "data may invalid, skip load cache."); goto errout; } data_size = data_head.size + sizeof(data_head); cache_data = zalloc(1, data_size); if (cache_data == NULL) { tlog(TLOG_ERROR, "malloc cache data failed %s", strerror(errno)); goto errout; } memcpy(&cache_data->head, &data_head, sizeof(data_head)); atomic_set(&cache_data->head.ref, 1); ret = read(fd, cache_data->data, data_head.size); if (ret != data_head.size) { tlog(TLOG_ERROR, "read cache data failed, %s", strerror(errno)); goto errout; } /* set cache unvisited, so that when refreshing ipset/nftset, reload ipset list by restarting smartdns */ cache_record.info.is_visited = 0; cache_record.info.domain[DNS_MAX_CNAME_LEN - 1] = '\0'; cache_record.info.dns_group_name[DNS_GROUP_NAME_LEN - 1] = '\0'; atomic_add(data_size, &dns_cache_head.mem_size); ret = callback(&cache_record, cache_data); dns_cache_data_put(cache_data); cache_data = NULL; if (ret != 0) { goto errout; } } return 0; errout: if (cache_data) { dns_cache_data_put(cache_data); } return -1; } static int _dns_cache_file_read(const char *file, dns_cache_read_callback callback) { int fd = -1; ssize_t ret = 0; off_t filesize = 0; fd = open(file, O_RDONLY); if (fd < 0) { return 0; } filesize = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); posix_fadvise(fd, 0, filesize, POSIX_FADV_WILLNEED | POSIX_FADV_SEQUENTIAL); struct dns_cache_file cache_file; ret = read(fd, &cache_file, sizeof(cache_file)); if (ret != sizeof(cache_file)) { tlog(TLOG_ERROR, "read cache head failed."); goto errout; } if (cache_file.magic != MAGIC_NUMBER) { tlog(TLOG_ERROR, "cache file is invalid."); goto errout; } if (strncmp(cache_file.version, dns_cache_file_version(), DNS_CACHE_VERSION_LEN) != 0) { tlog(TLOG_WARN, "cache version is different, skip load cache."); goto errout; } tlog(TLOG_INFO, "load cache file %s, total %d records", file, cache_file.cache_number); if (_dns_cache_read_record(fd, cache_file.cache_number, callback) != 0) { goto errout; } close(fd); return 0; errout: if (fd > 0) { close(fd); } return -1; } int dns_cache_load(const char *file) { return _dns_cache_file_read(file, _dns_cache_read_to_cache); } static int _dns_cache_write_record(int fd, uint32_t *cache_number, struct list_head *head) { struct dns_cache *dns_cache = NULL; struct dns_cache *tmp = NULL; struct dns_cache_record cache_record; memset(&cache_record, 0, sizeof(cache_record)); pthread_mutex_lock(&dns_cache_head.lock); list_for_each_entry_safe(dns_cache, tmp, head, list) { struct dns_cache_data *cache_data = dns_cache->cache_data; if (cache_data == NULL) { continue; } cache_record.magic = MAGIC_RECORD; memcpy(&cache_record.info, &dns_cache->info, sizeof(struct dns_cache_info)); ssize_t ret = write(fd, &cache_record, sizeof(cache_record)); if (ret != sizeof(cache_record)) { tlog(TLOG_ERROR, "write cache failed, %s", strerror(errno)); goto errout; } ret = write(fd, cache_data, sizeof(*cache_data) + cache_data->head.size); if (ret != (int)sizeof(*cache_data) + cache_data->head.size) { tlog(TLOG_ERROR, "write cache data failed, %s", strerror(errno)); goto errout; } (*cache_number)++; } pthread_mutex_unlock(&dns_cache_head.lock); return 0; errout: pthread_mutex_unlock(&dns_cache_head.lock); return -1; } static int _dns_cache_write_records(int fd, uint32_t *cache_number) { if (_dns_cache_write_record(fd, cache_number, &dns_cache_head.cache_list) != 0) { return -1; } return 0; } int dns_cache_save(const char *file, int check_lock) { int fd = -1; uint32_t cache_number = 0; tlog(TLOG_DEBUG, "write cache file %s", file); /* check lock */ if (check_lock == 1) { if (pthread_mutex_trylock(&dns_cache_head.lock) != 0) { return -1; } pthread_mutex_unlock(&dns_cache_head.lock); } fd = open(file, O_TRUNC | O_CREAT | O_WRONLY, 0640); if (fd < 0) { tlog(TLOG_ERROR, "create file %s failed, %s", file, strerror(errno)); goto errout; } struct dns_cache_file cache_file; memset(&cache_file, 0, sizeof(cache_file)); cache_file.magic = MAGIC_NUMBER; safe_strncpy(cache_file.version, dns_cache_file_version(), DNS_CACHE_VERSION_LEN); cache_file.cache_number = 0; if (lseek(fd, sizeof(cache_file), SEEK_SET) < 0) { tlog(TLOG_ERROR, "seek file %s failed, %s", file, strerror(errno)); goto errout; } if (_dns_cache_write_records(fd, &cache_number) != 0) { tlog(TLOG_ERROR, "write record to file %s failed.", file); goto errout; } if (lseek(fd, 0, SEEK_SET) < 0) { tlog(TLOG_ERROR, "seek file %s failed, %s", file, strerror(errno)); goto errout; } cache_file.cache_number = cache_number; if (write(fd, &cache_file, sizeof(cache_file)) != sizeof(cache_file)) { tlog(TLOG_ERROR, "write file head %s failed, %s, %d", file, strerror(errno), fd); goto errout; } tlog(TLOG_DEBUG, "wrote total %d records.", cache_number); close(fd); return 0; errout: if (fd > 0) { close(fd); } return -1; } static int _dns_cache_print(struct dns_cache_record *cache_record, struct dns_cache_data *cache_data) { char req_result[1024] = {0}; int left_len = sizeof(req_result); char *ip_msg = req_result; long i, j; if (cache_record->info.qtype == DNS_T_A || cache_record->info.qtype == DNS_T_AAAA) { char buff[DNS_PACKSIZE]; struct dns_packet *packet = (struct dns_packet *)buff; struct dns_rrs *rrs = NULL; int rr_count = 0; int ttl = 0; int ip_num = 0; int total_len = 0; int len = 0; int has_result = 0; char req_host[MAX_IP_LEN]; char name[DNS_MAX_CNAME_LEN] = {0}; if (dns_decode(packet, DNS_PACKSIZE, cache_data->data, cache_data->head.size) == 0) { total_len = snprintf(ip_msg, left_len, ", result: "); for (j = 1; j < DNS_RRS_OPT && packet; j++) { rrs = dns_get_rrs_start(packet, j, &rr_count); for (i = 0; i < rr_count && rrs && left_len > 0; i++, rrs = dns_get_rrs_next(packet, rrs)) { switch (rrs->type) { case DNS_T_A: { unsigned char ipv4_addr[4]; if (dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv4_addr) != 0) { continue; } const char *fmt = "%d.%d.%d.%d"; if (ip_num > 0) { fmt = ", %d.%d.%d.%d"; } len = snprintf(ip_msg + total_len, left_len, fmt, ipv4_addr[0], ipv4_addr[1], ipv4_addr[2], ipv4_addr[3]); ip_num++; has_result = 1; } break; case DNS_T_AAAA: { unsigned char ipv6_addr[16]; if (dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv6_addr) != 0) { continue; } const char *fmt = "%s"; if (ip_num > 0) { fmt = ", %s"; } req_host[0] = '\0'; inet_ntop(AF_INET6, ipv6_addr, req_host, sizeof(req_host)); len = snprintf(ip_msg + total_len, left_len, fmt, req_host); ip_num++; has_result = 1; } break; default: continue; } if (len < 0 || len >= left_len) { left_len = 0; break; } left_len -= len; total_len += len; } } } if (has_result == 0) { req_result[0] = '\0'; } } printf("domain: %s, qtype: %d, rcode: %d, ttl: %d, speed: %.1fms%s\n", cache_record->info.domain, cache_record->info.qtype, cache_record->info.rcode, cache_record->info.ttl, (float)cache_record->info.speed / 10, ip_msg); return 0; } int dns_cache_print(const char *file) { if (access(file, F_OK) != 0) { tlog(TLOG_ERROR, "cache file %s not exist.", file); return -1; } return _dns_cache_file_read(file, _dns_cache_print); } void dns_cache_destroy(void) { if (is_cache_init == 0) { return; } dns_cache_flush(); pthread_mutex_destroy(&dns_cache_head.lock); hash_table_free(dns_cache_head.cache_hash, free); is_cache_init = 0; } const char *dns_cache_file_version(void) { const char *version = "cache ver 1.3"; return version; } ================================================ FILE: src/dns_client/client_http2.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "client_http2.h" #include "client_socket.h" #include "client_tls.h" #include "conn_stream.h" #include "server_info.h" #include "smartdns/http2.h" #include /* BIO read callback for HTTP/2 */ static int _http2_bio_read(void *private_data, uint8_t *buf, int len) { struct dns_server_info *server_info = (struct dns_server_info *)private_data; return _dns_client_socket_ssl_recv(server_info, buf, len); } /* BIO write callback for HTTP/2 */ static int _http2_bio_write(void *private_data, const uint8_t *buf, int len) { struct dns_server_info *server_info = (struct dns_server_info *)private_data; return _dns_client_socket_ssl_send(server_info, buf, len); } /* Helper function to send buffered data from a conn_stream via HTTP/2 */ static int _dns_client_send_http2_stream(struct dns_server_info *server_info, struct dns_conn_stream *conn_stream, void *data, unsigned short len) { struct http2_ctx *http2_ctx = NULL; struct http2_stream *http2_stream = NULL; struct client_dns_server_flag_https *https_flag = &server_info->flags.https; char content_length[32]; pthread_mutex_lock(&server_info->lock); http2_ctx = server_info->http2_ctx; if (http2_ctx == NULL) { pthread_mutex_unlock(&server_info->lock); return -1; } /* Get reference to prevent it from being freed while we use it */ http2_ctx_get(http2_ctx); pthread_mutex_unlock(&server_info->lock); /* Create HTTP/2 stream */ http2_stream = http2_stream_new(http2_ctx); if (http2_stream == NULL) { if (errno != ENOSPC) { tlog(TLOG_WARN, "create http2 stream failed"); } http2_ctx_put(http2_ctx); return -1; } /* Set request headers */ snprintf(content_length, sizeof(content_length), "%d", len); struct http2_header_pair headers[] = {{"content-type", "application/dns-message"}, {"accept", "application/dns-message"}, {"content-length", content_length}, {NULL, NULL}}; if (http2_stream_set_request(http2_stream, "POST", https_flag->path, NULL, headers) < 0) { goto errout; } /* Write request body */ if (http2_stream_write_body(http2_stream, (const uint8_t *)data, len, 1) < 0) { goto errout; } pthread_mutex_lock(&server_info->lock); conn_stream->http2_stream = http2_stream; pthread_mutex_unlock(&server_info->lock); http2_stream_set_ex_data(http2_stream, conn_stream); http2_ctx_put(http2_ctx); return 0; errout: http2_stream_close(http2_stream); http2_ctx_put(http2_ctx); return -1; } /* Helper function to release a conn_stream and its references on error */ static void _dns_client_release_stream_on_error(struct dns_server_info *server_info, struct dns_conn_stream *stream) { if (!stream) { return; } pthread_mutex_lock(&server_info->lock); /* Remove from server list and release reference */ if (!list_empty(&stream->server_list)) { list_del_init(&stream->server_list); stream->server_info = NULL; _dns_client_conn_stream_put(stream); } pthread_mutex_unlock(&server_info->lock); /* Release the initial reference from creation */ _dns_client_conn_stream_put(stream); } /* Helper function to flush pending HTTP/2 writes */ static void _dns_client_flush_http2_writes(struct http2_ctx *http2_ctx) { int loop = 0; while (http2_ctx_want_write(http2_ctx) && loop++ < 10) { if (http2_ctx_poll(http2_ctx, NULL, 0, NULL) < 0) { break; } } } /* Helper function to send all buffered HTTP/2 requests */ static void _dns_client_send_buffered_http2_requests(struct dns_server_info *server_info) { struct dns_conn_stream *conn_stream = NULL; struct dns_conn_stream *tmp = NULL; while (1) { struct dns_conn_stream *target_stream = NULL; pthread_mutex_lock(&server_info->lock); list_for_each_entry_safe(conn_stream, tmp, &server_info->conn_stream_list, server_list) { if (conn_stream->http2_stream != NULL || conn_stream->send_buff.len <= 0) { continue; } target_stream = conn_stream; _dns_client_conn_stream_get(target_stream); break; } pthread_mutex_unlock(&server_info->lock); if (target_stream == NULL) { break; } /* Send buffered request using helper function */ if (_dns_client_send_http2_stream(server_info, target_stream, target_stream->send_buff.data, target_stream->send_buff.len) == 0) { /* Clear buffer as it's now in HTTP/2 stream buffer */ target_stream->send_buff.len = 0; _dns_client_conn_stream_put(target_stream); } else { /* Send failed, remove from buffer and clean up */ _dns_client_release_stream_on_error(server_info, target_stream); } } } /* Helper function to buffer data for HTTP/2 when connection is not ready */ static int _dns_client_http2_pending_data(struct dns_conn_stream *stream, struct dns_server_info *server_info, struct dns_query_struct *query, void *packet, int len) { struct epoll_event event; /* Validate input parameters */ if (len <= 0 || len > DNS_IN_PACKSIZE - 128) { errno = EINVAL; return -1; } if (DNS_TCP_BUFFER - stream->send_buff.len < len) { errno = ENOMEM; return -1; } if (client.epoll_fd <= 0) { errno = ECONNRESET; goto errout; } memcpy(stream->send_buff.data + stream->send_buff.len, packet, len); stream->send_buff.len += len; pthread_mutex_lock(&server_info->lock); if (server_info->fd <= 0) { pthread_mutex_unlock(&server_info->lock); errno = ECONNRESET; goto errout; } stream->server_info = server_info; if (list_empty(&stream->server_list)) { _dns_client_conn_stream_get(stream); list_add_tail(&stream->server_list, &server_info->conn_stream_list); } if (list_empty(&stream->query_list)) { _dns_client_conn_stream_get(stream); pthread_mutex_lock(&query->lock); stream->query = query; list_add_tail(&stream->query_list, &query->conn_stream_list); pthread_mutex_unlock(&query->lock); } memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); pthread_mutex_unlock(&server_info->lock); goto errout_put; } pthread_mutex_unlock(&server_info->lock); return 0; errout_put: /* Clean up stream on error */ pthread_mutex_lock(&server_info->lock); if (!list_empty(&stream->server_list)) { list_del_init(&stream->server_list); stream->server_info = NULL; _dns_client_conn_stream_put(stream); } if (!list_empty(&stream->query_list)) { if (stream->query) { pthread_mutex_lock(&stream->query->lock); list_del_init(&stream->query_list); pthread_mutex_unlock(&stream->query->lock); stream->query = NULL; } _dns_client_conn_stream_put(stream); } pthread_mutex_unlock(&server_info->lock); errout: return -1; } int _dns_client_send_http2(struct dns_server_info *server_info, struct dns_query_struct *query, void *packet, unsigned short len) { struct dns_conn_stream *stream = NULL; struct http2_ctx *http2_ctx = NULL; int ret = -1; if (len > DNS_IN_PACKSIZE - 128) { tlog(TLOG_ERROR, "packet size is invalid."); ret = -1; goto out; } /* Create connection stream for this request */ stream = _dns_client_conn_stream_new(); if (stream == NULL) { tlog(TLOG_ERROR, "malloc memory failed for http2 stream."); return -1; } stream->type = DNS_SERVER_HTTPS; /* If not connected, buffer the data and return */ if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { ret = _dns_client_http2_pending_data(stream, server_info, query, packet, len); goto out; } /* If connected but context not ready, buffer it too (will be flushed in process_http2) */ if (server_info->http2_ctx == NULL) { ret = _dns_client_http2_pending_data(stream, server_info, query, packet, len); goto out; } /* Send the request via HTTP/2 */ ret = _dns_client_send_http2_stream(server_info, stream, packet, len); if (ret < 0) { tlog(TLOG_DEBUG, "send http2 stream failed."); /* Fall back to buffering the data */ ret = _dns_client_http2_pending_data(stream, server_info, query, packet, len); goto out; } /* Now add stream to lists since HTTP/2 stream was successfully created */ pthread_mutex_lock(&server_info->lock); _dns_client_conn_stream_get(stream); stream->server_info = server_info; list_add_tail(&stream->server_list, &server_info->conn_stream_list); _dns_client_conn_stream_get(stream); pthread_mutex_lock(&query->lock); stream->query = query; list_add_tail(&stream->query_list, &query->conn_stream_list); pthread_mutex_unlock(&query->lock); pthread_mutex_unlock(&server_info->lock); /* Flush data immediately */ int loop = 0; while (http2_ctx_want_write(http2_ctx) && loop++ < 10) { if (http2_ctx_poll(http2_ctx, NULL, 0, NULL) < 0) { break; } } /* Check if there's pending write data, if so add EPOLLOUT event */ if (http2_ctx_want_write(http2_ctx)) { struct epoll_event event; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT; event.data.ptr = server_info; if (server_info->fd > 0) { if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); /* Continue anyway, data will be sent on next EPOLLIN */ } } } ret = 0; out: if (stream) { _dns_client_conn_stream_put(stream); } return ret; } static int _dns_client_http2_init_ctx(struct dns_server_info *server_info) { struct http2_ctx *http2_ctx = server_info->http2_ctx; struct client_dns_server_flag_https *https_flag = &server_info->flags.https; int ret = 0; if (http2_ctx != NULL) { return 0; } pthread_mutex_lock(&server_info->lock); if (server_info->http2_ctx == NULL) { http2_ctx = http2_ctx_client_new(https_flag->httphost, _http2_bio_read, _http2_bio_write, server_info, NULL); if (http2_ctx == NULL) { pthread_mutex_unlock(&server_info->lock); tlog(TLOG_ERROR, "init http2 context failed."); return -1; } server_info->http2_ctx = http2_ctx; /* server_info now owns the context (refcount=1 from _new) */ pthread_mutex_unlock(&server_info->lock); /* Perform HTTP/2 handshake */ ret = http2_ctx_handshake(http2_ctx); if (ret < 0) { tlog(TLOG_ERROR, "http2 handshake failed."); return -1; } } else { pthread_mutex_unlock(&server_info->lock); } return 0; } static int _dns_client_http2_process_write(struct dns_server_info *server_info) { struct http2_ctx *http2_ctx = NULL; int epoll_events = EPOLLIN; /* Send buffered requests */ _dns_client_send_buffered_http2_requests(server_info); pthread_mutex_lock(&server_info->lock); http2_ctx = server_info->http2_ctx; if (http2_ctx == NULL) { pthread_mutex_unlock(&server_info->lock); return 0; } http2_ctx_get(http2_ctx); pthread_mutex_unlock(&server_info->lock); /* Flush pending writes */ _dns_client_flush_http2_writes(http2_ctx); /* Update epoll events based on write status */ if (http2_ctx_want_write(http2_ctx)) { epoll_events |= EPOLLOUT; } if (server_info->fd > 0) { struct epoll_event mod_event; memset(&mod_event, 0, sizeof(mod_event)); mod_event.events = epoll_events; mod_event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &mod_event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); http2_ctx_put(http2_ctx); return -1; } } http2_ctx_put(http2_ctx); return 0; } static int _dns_client_http2_process_stream_one(struct dns_server_info *server_info, struct dns_conn_stream *conn_stream) { struct http2_stream *http2_stream = NULL; uint8_t response_body[DNS_IN_PACKSIZE]; int response_len = 0; int ret = 0; if (conn_stream == NULL) { return 1; } http2_stream = conn_stream->http2_stream; if (http2_stream == NULL || conn_stream->query == NULL) { return 1; } /* Check HTTP status code first */ int status = http2_stream_get_status(http2_stream); if (status > 0 && status != 200) { tlog(TLOG_WARN, "http2 server query from %s:%d failed, server return http code: %d", server_info->ip, server_info->port, status); server_info->prohibit = 1; return 1; } /* Read response body */ response_len = http2_stream_read_body(http2_stream, response_body, sizeof(response_body)); if (response_len <= 0) { /* Error or no data - check if stream has ended */ goto out; } /* Process DNS response */ ret = _dns_client_recv(server_info, response_body, response_len, &server_info->addr, server_info->ai_addrlen); if (ret != 0) { tlog(TLOG_ERROR, "process dns response failed"); } out: if (http2_stream_is_end(http2_stream)) { return 1; } return 0; } static int _dns_client_http2_process_read(struct dns_server_info *server_info) { struct http2_ctx *http2_ctx = NULL; struct http2_poll_item poll_items[128]; int poll_count = 0; int loop_count = 0; const int MAX_LOOP_COUNT = 512; struct dns_conn_stream *conn_stream = NULL; int ret = 0; int i = 0; pthread_mutex_lock(&server_info->lock); http2_ctx = server_info->http2_ctx; if (http2_ctx == NULL) { pthread_mutex_unlock(&server_info->lock); return 0; } http2_ctx_get(http2_ctx); pthread_mutex_unlock(&server_info->lock); /* Ensure handshake is complete before polling */ ret = http2_ctx_handshake(http2_ctx); if (ret == 0) { /* Handshake in progress, need more data */ http2_ctx_put(http2_ctx); return 0; } else if (ret < 0) { tlog(TLOG_DEBUG, "http2 handshake failed."); http2_ctx_put(http2_ctx); return -1; } /* Poll and process streams until no more ready */ while (loop_count++ < MAX_LOOP_COUNT) { /* Poll for stream readiness */ ret = http2_ctx_poll_readable(http2_ctx, poll_items, 128, &poll_count); if (ret < 0) { if (ret != HTTP2_ERR_EOF) { tlog(TLOG_DEBUG, "http2 poll failed, ret=%d", ret); } http2_ctx_put(http2_ctx); return -1; } if (poll_count == 0) { break; } /* Process each ready stream */ for (i = 0; i < poll_count; i++) { struct http2_stream *stream = poll_items[i].stream; if (stream == NULL) { continue; } conn_stream = (struct dns_conn_stream *)http2_stream_get_ex_data(stream); if (conn_stream == NULL) { http2_stream_put(stream); continue; } /* Get reference to conn_stream to prevent it from being freed while we use it */ _dns_client_conn_stream_get(conn_stream); if (poll_items[i].readable) { int stream_ended = _dns_client_http2_process_stream_one(server_info, conn_stream); if (stream_ended) { int need_put = 0; pthread_mutex_lock(&server_info->lock); if (!list_empty(&conn_stream->server_list)) { list_del_init(&conn_stream->server_list); conn_stream->server_info = NULL; need_put = 1; } pthread_mutex_unlock(&server_info->lock); if (need_put) { _dns_client_conn_stream_put(conn_stream); } } } /* Release our reference */ _dns_client_conn_stream_put(conn_stream); http2_stream_put(stream); } if (poll_count < 128) { break; } } http2_ctx_put(http2_ctx); return 0; } int _dns_client_process_http2(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) { if (server_info->http2_ctx == NULL) { if (_dns_client_http2_init_ctx(server_info) < 0) { return -1; } } if (event->events & EPOLLOUT) { if (_dns_client_http2_process_write(server_info) < 0) { return -1; } } /* Always process read, as write might have read data (e.g. WINDOW_UPDATE), or there might be pending data in SSL/HTTP2 buffers */ if (_dns_client_http2_process_read(server_info) < 0) { return -1; } return 0; } ================================================ FILE: src/dns_client/client_http2.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_HTTP2_H #define _DNS_CLIENT_HTTP2_H #include "dns_client.h" #include "server_info.h" #include #ifdef __cplusplus extern "C" { #endif /** * Send DNS query over HTTP/2 * @param server_info Server information * @param query DNS query structure * @param packet DNS query packet * @param len Packet length * @return 0 on success, -1 on error */ int _dns_client_send_http2(struct dns_server_info *server_info, struct dns_query_struct *query, void *packet, unsigned short len); /** * Process HTTP/2 for a server (handles handshake and all streams) * @param server_info Server information * @param event Epoll event * @param now Current time * @return 0 on success, -1 on error */ int _dns_client_process_http2(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now); #ifdef __cplusplus } #endif #endif /* _DNS_CLIENT_HTTP2_H */ ================================================ FILE: src/dns_client/client_http3.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "client_http3.h" #include "client_quic.h" #include "conn_stream.h" #include "smartdns/http_parse.h" int _dns_client_send_http3(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet, unsigned short len) { #if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC) int http_len = 0; int ret = 0; unsigned char inpacket_data[DNS_IN_PACKSIZE]; struct client_dns_server_flag_https *https_flag = NULL; struct http_head *http_head = NULL; if (len > sizeof(inpacket_data) - 128) { tlog(TLOG_ERROR, "packet size is invalid."); goto errout; } https_flag = &server_info->flags.https; http_head = http_head_init(4096, HTTP_VERSION_3_0); if (http_head == NULL) { tlog(TLOG_ERROR, "init http head failed."); goto errout; } http_head_set_method(http_head, HTTP_METHOD_POST); http_head_set_url(http_head, https_flag->path); http_head_set_head_type(http_head, HTTP_HEAD_REQUEST); http_head_add_fields(http_head, ":authority", https_flag->httphost); http_head_add_fields(http_head, "user-agent", "smartdns"); http_head_add_fields(http_head, "content-type", "application/dns-message"); http_head_add_fields(http_head, "accept", "application/dns-message"); http_head_add_fields(http_head, "accept-encoding", "identity"); http_head_set_data(http_head, packet, len); http_len = http_head_serialize(http_head, inpacket_data, DNS_IN_PACKSIZE); if (http_len <= 0) { tlog(TLOG_ERROR, "serialize http head failed."); goto errout; } ret = _dns_client_send_quic_data(query, server_info, inpacket_data, http_len); http_head_destroy(http_head); return ret; errout: if (http_head) { http_head_destroy(http_head); } return -1; #else tlog(TLOG_ERROR, "http3 is not supported."); #endif return 0; } #if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC) int _dns_client_process_recv_http3(struct dns_server_info *server_info, struct dns_conn_stream *conn_stream) { int ret = 0; struct http_head *http_head = NULL; uint8_t *pkg_data = NULL; int pkg_len = 0; http_head = http_head_init(4096, HTTP_VERSION_3_0); if (http_head == NULL) { goto errout; } ret = http_head_parse(http_head, conn_stream->recv_buff.data, conn_stream->recv_buff.len); if (ret < 0) { if (ret == -1) { goto out; } else if (ret == -3) { /* repsone is too large */ tlog(TLOG_DEBUG, "http3 response is too large."); conn_stream->recv_buff.len = 0; _dns_client_conn_stream_put(conn_stream); goto errout; } tlog(TLOG_DEBUG, "remote server not supported."); goto errout; } if (http_head_get_httpcode(http_head) == 0) { /* invalid http3 response */ server_info->prohibit = 1; goto errout; } if (http_head_get_httpcode(http_head) != 200) { tlog(TLOG_WARN, "http3 server query from %s:%d failed, server return http code : %d, %s", server_info->ip, server_info->port, http_head_get_httpcode(http_head), http_head_get_httpcode_msg(http_head)); server_info->prohibit = 1; goto errout; } pkg_data = (uint8_t *)http_head_get_data(http_head); pkg_len = http_head_get_data_len(http_head); if (pkg_data == NULL || pkg_len <= 0) { goto errout; } if (_dns_client_recv(server_info, pkg_data, pkg_len, &server_info->addr, server_info->ai_addrlen) != 0) { goto errout; } out: http_head_destroy(http_head); return 0; errout: if (http_head) { http_head_destroy(http_head); } return -1; } #endif ================================================ FILE: src/dns_client/client_http3.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_HTTP3_H_ #define _DNS_CLIENT_HTTP3_H_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_send_http3(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet, unsigned short len); int _dns_client_process_recv_http3(struct dns_server_info *server_info, struct dns_conn_stream *conn_stream); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/client_https.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "client_https.h" #include "client_socket.h" #include "client_tls.h" #include "server_info.h" #include "smartdns/http_parse.h" int _dns_client_format_https_packet(struct dns_server_info *server_info, void *packet, unsigned short len, unsigned char *outpacket, int outpacket_max) { int http_len = 0; struct client_dns_server_flag_https *https_flag = NULL; if (len > outpacket_max - 2) { tlog(TLOG_ERROR, "packet size is invalid."); return -1; } https_flag = &server_info->flags.https; http_len = snprintf((char *)outpacket, outpacket_max, "POST %s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: smartdns\r\n" "Content-Type: application/dns-message\r\n" "Accept: application/dns-message\r\n" "Content-Length: %d\r\n" "\r\n", https_flag->path, https_flag->httphost, len); if (http_len < 0 || http_len >= outpacket_max) { tlog(TLOG_ERROR, "http header size is invalid."); return -1; } memcpy(outpacket + http_len, packet, len); http_len += len; return http_len; } int _dns_client_send_http1(struct dns_server_info *server_info, void *packet, unsigned short len) { int send_len = 0; int http_len = 0; unsigned char inpacket_data[DNS_IN_PACKSIZE]; unsigned char *inpacket = inpacket_data; http_len = _dns_client_format_https_packet(server_info, packet, len, inpacket, sizeof(inpacket_data)); if (http_len < 0) { return -1; } if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { return _dns_client_send_data_to_buffer(server_info, inpacket, http_len); } if (server_info->ssl == NULL) { errno = EINVAL; return -1; } send_len = _dns_client_socket_ssl_send(server_info, inpacket, http_len); if (send_len <= 0) { if (errno == EAGAIN || errno == EPIPE || server_info->ssl == NULL) { /* save data to buffer, and retry when EPOLLOUT is available */ return _dns_client_send_data_to_buffer(server_info, inpacket, http_len); } else if (server_info->ssl && errno != ENOMEM) { _dns_client_shutdown_socket(server_info); } return -1; } else if (send_len < http_len) { /* save remain data to buffer, and retry when EPOLLOUT is available */ return _dns_client_send_data_to_buffer(server_info, inpacket + send_len, http_len - send_len); } return 0; } ================================================ FILE: src/dns_client/client_https.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_HTTPS_H_ #define _DNS_CLIENT_HTTPS_H_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_send_http1(struct dns_server_info *server_info, void *packet, unsigned short len); int _dns_client_format_https_packet(struct dns_server_info *server_info, void *packet, unsigned short len, unsigned char *outpacket, int outpacket_max); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/client_mdns.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "client_mdns.h" #include "server_info.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include #include #include #include #include #include int _dns_client_create_socket_udp_mdns(struct dns_server_info *server_info) { int fd = -1; struct epoll_event event; const int on = 1; const int val = 1; const int priority = SOCKET_PRIORITY; const int ip_tos = SOCKET_IP_TOS; fd = socket(server_info->ai_family, SOCK_DGRAM, 0); if (fd < 0) { tlog(TLOG_ERROR, "create socket failed, %s", strerror(errno)); goto errout; } if (set_fd_nonblock(fd, 1) != 0) { tlog(TLOG_ERROR, "set socket non block failed, %s", strerror(errno)); goto errout; } struct ifreq ifr; memset(&ifr, 0, sizeof(struct ifreq)); safe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name)); ioctl(fd, SIOCGIFINDEX, &ifr); if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) { tlog(TLOG_ERROR, "bind socket to device %s failed, %s\n", ifr.ifr_name, strerror(errno)); goto errout; } server_info->fd = fd; server_info->status = DNS_SERVER_STATUS_CONNECTIONLESS; server_info->security_status = DNS_CLIENT_SERVER_SECURITY_NOT_APPLICABLE; memset(&event, 0, sizeof(event)); event.events = EPOLLIN; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed."); return -1; } setsockopt(server_info->fd, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on)); setsockopt(server_info->fd, SOL_IP, IP_TTL, &val, sizeof(val)); setsockopt(server_info->fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); setsockopt(server_info->fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); setsockopt(server_info->fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, sizeof(val)); if (server_info->ai_family == AF_INET6) { /* for receiving ip ttl value */ setsockopt(server_info->fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)); setsockopt(server_info->fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on)); setsockopt(server_info->fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on)); setsockopt(server_info->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)); } return 0; errout: if (fd > 0) { close(fd); } server_info->fd = -1; server_info->status = DNS_SERVER_STATUS_DISCONNECTED; return -1; } int _dns_client_send_udp_mdns(struct dns_server_info *server_info, void *packet, int len) { int send_len = 0; const struct sockaddr *addr = &server_info->addr; socklen_t addrlen = server_info->ai_addrlen; if (server_info->fd <= 0) { return -1; } send_len = sendto(server_info->fd, packet, len, 0, addr, addrlen); if (send_len != len) { goto errout; } return 0; errout: return -1; } int _dns_client_add_mdns_server(void) { struct client_dns_server_flags server_flags; int ret = 0; struct ifaddrs *ifaddr = NULL; struct ifaddrs *ifa = NULL; if (dns_conf.mdns_lookup != 1) { return 0; } memset(&server_flags, 0, sizeof(server_flags)); server_flags.server_flag |= SERVER_FLAG_EXCLUDE_DEFAULT | DOMAIN_FLAG_IPSET_IGN | DOMAIN_FLAG_NFTSET_INET_IGN; if (dns_client_add_group(DNS_SERVER_GROUP_MDNS) != 0) { tlog(TLOG_ERROR, "add default server group failed."); goto errout; } #ifdef TEST safe_strncpy(server_flags.ifname, "lo", sizeof(server_flags.ifname)); ret = _dns_client_server_add(DNS_MDNS_IP, "", DNS_MDNS_PORT, DNS_SERVER_MDNS, &server_flags); if (ret != 0) { tlog(TLOG_ERROR, "add mdns server to %s failed.", "lo"); goto errout; } if (dns_client_add_to_group(DNS_SERVER_GROUP_MDNS, DNS_MDNS_IP, DNS_MDNS_PORT, DNS_SERVER_MDNS, &server_flags) != 0) { tlog(TLOG_ERROR, "add mdns server to group %s failed.", DNS_SERVER_GROUP_MDNS); goto errout; } return 0; #endif if (getifaddrs(&ifaddr) == -1) { goto errout; } for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { const unsigned char *addr = NULL; int addr_len = 0; if (ifa->ifa_addr == NULL) { continue; } if (AF_INET != ifa->ifa_addr->sa_family && AF_INET6 != ifa->ifa_addr->sa_family) { continue; } addr = (const unsigned char *)&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; addr_len = sizeof(struct in_addr); // Skip the local interface if (strcmp(ifa->ifa_name, "lo") == 0 || strcmp(ifa->ifa_name, "localhost") == 0) { continue; } if (is_private_addr(addr, addr_len) == 0) { continue; } safe_strncpy(server_flags.ifname, ifa->ifa_name, sizeof(server_flags.ifname)); char *dns_ip[] = {DNS_MDNS_IP, DNS_MDNS_IP6, 0}; for (int i = 0; dns_ip[i] != NULL; i++) { ret = _dns_client_server_add(dns_ip[i], "", DNS_MDNS_PORT, DNS_SERVER_MDNS, &server_flags); if (ret != 0) { tlog(TLOG_ERROR, "add mdns server failed for %s.", dns_ip[i]); goto errout; } if (dns_client_add_to_group(DNS_SERVER_GROUP_MDNS, dns_ip[i], DNS_MDNS_PORT, DNS_SERVER_MDNS, &server_flags) != 0) { tlog(TLOG_ERROR, "add mdns server to group failed for %s.", dns_ip[i]); goto errout; } } } freeifaddrs(ifaddr); return 0; errout: if (ifaddr) { freeifaddrs(ifaddr); } return -1; } ================================================ FILE: src/dns_client/client_mdns.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_MDNS_ #define _DNS_CLIENT_MDNS_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_create_socket_udp_mdns(struct dns_server_info *server_info); int _dns_client_send_udp_mdns(struct dns_server_info *server_info, void *packet, int len); int _dns_client_add_mdns_server(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/client_quic.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/http_parse.h" #include "smartdns/util.h" #include "client_http3.h" #include "client_quic.h" #include "client_socket.h" #include "client_tls.h" #include "conn_stream.h" #include "server_info.h" #include #include #include #include #include #include #include #include #if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC) static int _dns_client_quic_bio_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t num_msg, uint64_t flags, size_t *msgs_processed) { struct dns_server_info *server_info = NULL; int total_len = 0; int len = 0; struct sockaddr_storage from; socklen_t from_len = sizeof(from); server_info = (struct dns_server_info *)BIO_get_data(bio); if (server_info == NULL) { tlog(TLOG_ERROR, "server info is null, %s", server_info->ip); return 0; } *msgs_processed = 0; for (size_t i = 0; i < num_msg; i++) { len = proxy_conn_recvfrom(server_info->proxy, msg[i].data, msg[i].data_len, 0, (struct sockaddr *)&from, &from_len); if (len < 0) { if (*msgs_processed == 0) { ERR_raise(ERR_LIB_SYS, errno); total_len = 0; } if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } if (errno == EPIPE || errno == ECONNRESET) { /* Ignore broken pipe and connection reset errors */ tlog(TLOG_DEBUG, "recvmsg broken pipe or connection reset, %s", server_info->ip); return total_len; } tlog(TLOG_ERROR, "recvmsg failed, %s", strerror(errno)); return 0; } msg[i].data_len = len; total_len += len; *msgs_processed += 1; } return total_len; } static int _dns_client_quic_bio_sendmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t num_msg, uint64_t flags, size_t *msgs_processed) { struct dns_server_info *server_info = NULL; int total_len = 0; int len = 0; const struct sockaddr *addr = NULL; socklen_t addrlen = 0; *msgs_processed = 0; server_info = (struct dns_server_info *)BIO_get_data(bio); if (server_info == NULL) { tlog(TLOG_ERROR, "server info is null, %s", server_info->ip); return 0; } addr = &server_info->addr; addrlen = server_info->ai_addrlen; for (size_t i = 0; i < num_msg; i++) { len = proxy_conn_sendto(server_info->proxy, msg[i].data, msg[i].data_len, 0, addr, addrlen); if (len < 0) { if (*msgs_processed == 0) { ERR_raise(ERR_LIB_SYS, errno); total_len = 0; } if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } if (errno == EPIPE || errno == ECONNRESET) { /* Ignore broken pipe and connection reset errors */ tlog(TLOG_DEBUG, "sendmsg broken pipe or connection reset, %s", server_info->ip); return total_len; } tlog(TLOG_ERROR, "sendmsg failed, %s", strerror(errno)); return 0; } total_len += len; *msgs_processed += 1; } return total_len; } static long _dns_client_quic_bio_ctrl(BIO *bio, int cmd, long num, void *ptr) { struct dns_server_info *server_info = NULL; long ret = 0; server_info = (struct dns_server_info *)BIO_get_data(bio); if (server_info == NULL) { tlog(TLOG_ERROR, "server info is null."); return -1; } switch (cmd) { case BIO_CTRL_DGRAM_GET_MTU: break; default: break; } return ret; } static int _dns_client_setup_quic_ssl_bio(struct dns_server_info *server_info, SSL *ssl, int fd, struct proxy_conn *proxy) { BIO_METHOD *bio_method_alloc = NULL; BIO_METHOD *bio_method = server_info->bio_method; BIO *udp_socket_bio = NULL; if (ssl == NULL) { tlog(TLOG_ERROR, "ssl is null, %s", server_info->ip); return -1; } if (proxy == NULL) { if (SSL_set_fd(ssl, fd) == 0) { tlog(TLOG_ERROR, "ssl set fd failed."); goto errout; } return 0; } if (bio_method == NULL) { bio_method_alloc = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "udp-proxy"); if (bio_method_alloc == NULL) { tlog(TLOG_ERROR, "create bio method failed."); goto errout; } bio_method = bio_method_alloc; BIO_meth_set_sendmmsg(bio_method, _dns_client_quic_bio_sendmmsg); BIO_meth_set_recvmmsg(bio_method, _dns_client_quic_bio_recvmmsg); BIO_meth_set_ctrl(bio_method, _dns_client_quic_bio_ctrl); } udp_socket_bio = BIO_new(bio_method); if (udp_socket_bio == NULL) { tlog(TLOG_ERROR, "create udp_socket_bio failed."); goto errout; } BIO_set_data(udp_socket_bio, (void *)server_info); BIO_set_init(udp_socket_bio, 1); SSL_set_bio(ssl, udp_socket_bio, udp_socket_bio); server_info->bio_method = bio_method; return 0; errout: if (bio_method_alloc) { BIO_meth_free(bio_method_alloc); } if (udp_socket_bio) { BIO_free(udp_socket_bio); } return -1; } #endif int _dns_client_create_socket_quic(struct dns_server_info *server_info, const char *hostname, const char *alpn) { #if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC) int fd = -1; unsigned char alpn_data[DNS_MAX_ALPN_LEN]; int32_t alpn_len = 0; struct epoll_event event; SSL *ssl = NULL; struct proxy_conn *proxy = NULL; int ret = -1; if (server_info->ssl_ctx == NULL) { tlog(TLOG_ERROR, "create ssl ctx failed, %s", server_info->ip); goto errout; } if (server_info->proxy_name[0] != '\0') { proxy = proxy_conn_new(server_info->proxy_name, server_info->ip, server_info->port, 1, 1); if (proxy == NULL) { tlog(TLOG_ERROR, "create proxy failed, %s, proxy: %s", server_info->ip, server_info->proxy_name); goto errout; } fd = proxy_conn_get_fd(proxy); } else { fd = socket(server_info->ai_family, SOCK_DGRAM, IPPROTO_UDP); } if (fd < 0) { tlog(TLOG_ERROR, "create socket failed, %s", strerror(errno)); goto errout; } if (set_fd_nonblock(fd, 1) != 0) { tlog(TLOG_ERROR, "set socket non block failed, %s", strerror(errno)); goto errout; } ssl = SSL_new(server_info->ssl_ctx); if (ssl == NULL) { tlog(TLOG_ERROR, "new ssl failed, %s", server_info->ip); goto errout; } if (server_info->so_mark >= 0) { unsigned int so_mark = server_info->so_mark; if (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) { tlog(TLOG_DEBUG, "set socket mark failed, %s", strerror(errno)); } } if (proxy) { ret = proxy_conn_connect(proxy); } else { ret = connect(fd, &server_info->addr, server_info->ai_addrlen); } if (ret != 0) { if (errno != EINPROGRESS) { tlog(TLOG_DEBUG, "connect %s failed, %s", server_info->ip, strerror(errno)); goto errout; } } SSL_set_blocking_mode(ssl, 0); SSL_set_default_stream_mode(ssl, SSL_DEFAULT_STREAM_MODE_NONE); if (_dns_client_setup_quic_ssl_bio(server_info, ssl, fd, proxy) != 0) { tlog(TLOG_ERROR, "ssl set fd failed."); goto errout; } SSL_set_connect_state(ssl); /* reuse ssl session */ if (server_info->ssl_session) { SSL_set_session(ssl, server_info->ssl_session); } SSL_set_mode(ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE); if (hostname[0] != 0) { SSL_set_tlsext_host_name(ssl, hostname); } SSL_set1_host(ssl, hostname); if (alpn == NULL) { tlog(TLOG_INFO, "alpn is null."); goto errout; } alpn_len = strnlen(alpn, DNS_MAX_ALPN_LEN - 1); alpn_data[0] = alpn_len; memcpy(alpn_data + 1, alpn, alpn_len); alpn_len++; if (SSL_set_alpn_protos(ssl, alpn_data, alpn_len)) { tlog(TLOG_INFO, "SSL_set_alpn_protos failed."); goto errout; } if (server_info->ssl) { SSL_free(server_info->ssl); server_info->ssl = NULL; } server_info->fd = fd; server_info->ssl = ssl; server_info->ssl_write_len = -1; server_info->status = DNS_SERVER_STATUS_CONNECTING; server_info->proxy = proxy; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed."); goto errout; } tlog(TLOG_DEBUG, "quic server %s:%d connecting.\n", server_info->ip, server_info->port); return 0; errout: if (server_info->fd > 0) { server_info->fd = -1; } if (server_info->ssl) { server_info->ssl = NULL; } server_info->status = DNS_SERVER_STATUS_INIT; server_info->proxy = NULL; server_info->ssl_write_len = -1; if (fd > 0 && proxy == NULL) { close(fd); } if (ssl) { SSL_free(ssl); } if (proxy) { proxy_conn_free(proxy); } return -1; #else return -1; #endif } #if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC) static int _dns_client_process_quic_poll(struct dns_server_info *server_info) { LIST_HEAD(processed_list); static int MAX_POLL_ITEM_COUNT = 128; SSL_POLL_ITEM poll_items[MAX_POLL_ITEM_COUNT]; memset(poll_items, 0, sizeof(poll_items)); static const struct timeval nz_timeout = {0, 0}; int poll_ret = 0; int ret = 0; struct dns_conn_stream *conn_stream = NULL; struct dns_conn_stream *tmp = NULL; while (true) { int poll_item_count = 0; size_t poll_process_count = 0; size_t poll_retcount = 0; pthread_mutex_lock(&server_info->lock); list_for_each_entry_safe(conn_stream, tmp, &server_info->conn_stream_list, server_list) { if (conn_stream->quic_stream == NULL) { continue; } if (poll_item_count >= MAX_POLL_ITEM_COUNT) { break; } poll_items[poll_item_count].desc = SSL_as_poll_descriptor(conn_stream->quic_stream); poll_items[poll_item_count].events = SSL_POLL_EVENT_R; poll_items[poll_item_count].revents = 0; poll_item_count++; list_del_init(&conn_stream->server_list); list_add_tail(&conn_stream->server_list, &processed_list); } pthread_mutex_unlock(&server_info->lock); if (poll_item_count <= 0) { _ssl_do_handevent(server_info); break; } ret = SSL_poll(poll_items, poll_item_count, sizeof(SSL_POLL_ITEM), &nz_timeout, 0, &poll_retcount); if (ret <= 0) { tlog(TLOG_DEBUG, "SSL_poll failed, %d", ret); goto errout; } for (int i = 0; i < MAX_POLL_ITEM_COUNT && poll_process_count < poll_retcount; i++) { if (poll_items[i].revents & SSL_POLL_EVENT_R) { poll_process_count++; conn_stream = SSL_get_ex_data(poll_items[i].desc.value.ssl, 0); if (conn_stream == NULL) { tlog(TLOG_DEBUG, "conn stream is null"); SSL_free(poll_items[i].desc.value.ssl); continue; } int read_len = _dns_client_socket_ssl_recv_ext(server_info, poll_items[i].desc.value.ssl, conn_stream->recv_buff.data, DNS_TCP_BUFFER); if (read_len < 0) { if (errno == EAGAIN) { continue; } tlog(TLOG_ERROR, "recv failed, %s", strerror(errno)); continue; } conn_stream->recv_buff.len += read_len; if (conn_stream->query == NULL) { list_del_init(&conn_stream->server_list); _dns_client_conn_stream_put(conn_stream); continue; } if (server_info->type == DNS_SERVER_HTTP3) { ret = _dns_client_process_recv_http3(server_info, conn_stream); if (ret != 0) { continue; } } else if (server_info->type == DNS_SERVER_QUIC) { unsigned short qid = htons(conn_stream->query->sid); int msg_len = ntohs(*((unsigned short *)(conn_stream->recv_buff.data))); if (msg_len <= 0 || msg_len >= DNS_IN_PACKSIZE) { /* data len is invalid */ continue; } if (msg_len > conn_stream->recv_buff.len - 2) { errno = EAGAIN; /* len is not expected, wait and recv */ continue; } memcpy(conn_stream->recv_buff.data + 2, &qid, 2); if (_dns_client_recv(server_info, conn_stream->recv_buff.data + 2, conn_stream->recv_buff.len - 2, &server_info->addr, server_info->ai_addrlen) != 0) { continue; } } /* process succeed, delete from processed_list*/ list_del_init(&conn_stream->server_list); _dns_client_conn_stream_put(conn_stream); } } } poll_ret = 0; goto out; errout: poll_ret = -1; out: pthread_mutex_lock(&server_info->lock); if (list_empty(&processed_list)) { pthread_mutex_unlock(&server_info->lock); return 0; } list_splice_tail(&processed_list, &server_info->conn_stream_list); pthread_mutex_unlock(&server_info->lock); return poll_ret; } int _dns_client_process_quic(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) { if (event->events & EPOLLIN) { /* connection is closed, reconnect */ if (SSL_get_shutdown(server_info->ssl) != 0) { int ret = 0; _dns_client_close_socket_ext(server_info, 1); pthread_mutex_lock(&server_info->lock); server_info->recv_buff.len = 0; if (!list_empty(&server_info->conn_stream_list)) { /* still remain request data, reconnect and send*/ ret = _dns_client_create_socket(server_info); } else { ret = 0; } pthread_mutex_unlock(&server_info->lock); tlog(TLOG_DEBUG, "quic server %s:%d peer close", server_info->ip, server_info->port); return ret; } if (_dns_client_process_quic_poll(server_info) != 0) { goto errout; } } if (event->events & EPOLLOUT) { int epoll_events = EPOLLIN; struct dns_conn_stream *conn_stream = NULL; pthread_mutex_lock(&server_info->lock); list_for_each_entry(conn_stream, &server_info->conn_stream_list, server_list) { if (conn_stream->quic_stream != NULL) { continue; } if (conn_stream->send_buff.len <= 0) { continue; } conn_stream->quic_stream = SSL_new_stream(server_info->ssl, 0); if (conn_stream->quic_stream == NULL) { pthread_mutex_unlock(&server_info->lock); goto errout; } SSL_set_ex_data(conn_stream->quic_stream, 0, conn_stream); int send_len = _dns_client_socket_ssl_send_ext(server_info, conn_stream->quic_stream, conn_stream->send_buff.data, conn_stream->send_buff.len, SSL_WRITE_FLAG_CONCLUDE); if (send_len < 0) { if (errno == EAGAIN) { epoll_events = EPOLLIN | EPOLLOUT; _ssl_do_handevent(server_info); } } if (send_len < conn_stream->send_buff.len) { conn_stream->send_buff.len -= send_len; memmove(conn_stream->send_buff.data, conn_stream->send_buff.data + send_len, conn_stream->send_buff.len); epoll_events = EPOLLIN | EPOLLOUT; } else { conn_stream->send_buff.len = 0; } } pthread_mutex_unlock(&server_info->lock); if (server_info->fd > 0) { /* clear epollout event */ struct epoll_event mod_event; memset(&mod_event, 0, sizeof(mod_event)); mod_event.events = epoll_events; mod_event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &mod_event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); goto errout; } } } return 0; errout: return -1; } #endif #if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC) static int _dns_client_quic_pending_data(struct dns_conn_stream *stream, struct dns_server_info *server_info, struct dns_query_struct *query, void *packet, int len) { struct epoll_event event; if (DNS_TCP_BUFFER - stream->send_buff.len < len) { errno = ENOMEM; return -1; } if (client.epoll_fd <= 0) { errno = ECONNRESET; goto errout; } memcpy(stream->send_buff.data + stream->send_buff.len, packet, len); stream->send_buff.len += len; pthread_mutex_lock(&server_info->lock); if (server_info->fd <= 0) { pthread_mutex_unlock(&server_info->lock); errno = ECONNRESET; goto errout; } stream->server_info = server_info; if (list_empty(&stream->server_list)) { list_add_tail(&stream->server_list, &server_info->conn_stream_list); _dns_client_conn_stream_get(stream); } if (list_empty(&stream->query_list)) { pthread_mutex_lock(&query->lock); stream->query = query; list_add_tail(&stream->query_list, &query->conn_stream_list); pthread_mutex_unlock(&query->lock); _dns_client_conn_stream_get(stream); } memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); pthread_mutex_unlock(&server_info->lock); goto errout_put; } pthread_mutex_unlock(&server_info->lock); return 0; errout_put: pthread_mutex_lock(&server_info->lock); if (!list_empty(&stream->server_list)) { list_del_init(&stream->server_list); stream->server_info = NULL; _dns_client_conn_stream_put(stream); } if (!list_empty(&stream->query_list)) { if (stream->query) { pthread_mutex_lock(&stream->query->lock); list_del_init(&stream->query_list); pthread_mutex_unlock(&stream->query->lock); stream->query = NULL; } _dns_client_conn_stream_put(stream); } pthread_mutex_unlock(&server_info->lock); errout: return -1; } int _dns_client_send_quic_data(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet, unsigned short len) { int send_len = 0; int ret = 0; _dns_client_conn_server_streams_free(server_info, query); if (server_info->ssl == NULL) { tlog(TLOG_DEBUG, "ssl is invalid, server %s", server_info->ip); return -1; } struct dns_conn_stream *stream = _dns_client_conn_stream_new(); if (stream == NULL) { tlog(TLOG_ERROR, "malloc memory failed."); return -1; } stream->type = server_info->type; if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { ret = _dns_client_quic_pending_data(stream, server_info, query, packet, len); goto out; } /* run quic handevent */ _ssl_do_handevent(server_info); SSL *quic_stream = SSL_new_stream(server_info->ssl, 0); if (quic_stream == NULL) { struct epoll_event event; _dns_client_shutdown_socket(server_info); ret = _dns_client_quic_pending_data(stream, server_info, query, packet, len); if (ret != 0) { errno = ECONNRESET; goto out; } /* clear epollout event */ memset(&event, 0, sizeof(event)); event.events = EPOLLIN; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) { if (errno == ENOENT) { goto out; } tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); ret = -1; } goto out; } pthread_mutex_lock(&server_info->lock); stream->server_info = server_info; list_add_tail(&stream->server_list, &server_info->conn_stream_list); _dns_client_conn_stream_get(stream); pthread_mutex_lock(&query->lock); stream->query = query; list_add_tail(&stream->query_list, &query->conn_stream_list); pthread_mutex_unlock(&query->lock); _dns_client_conn_stream_get(stream); pthread_mutex_unlock(&server_info->lock); /* bind stream */ SSL_set_ex_data(quic_stream, 0, stream); stream->quic_stream = quic_stream; send_len = _dns_client_socket_ssl_send_ext(server_info, quic_stream, packet, len, SSL_WRITE_FLAG_CONCLUDE); if (send_len <= 0) { if (errno == EAGAIN || server_info->ssl == NULL) { /* save data to buffer, and retry when EPOLLOUT is available */ ret = _dns_client_quic_pending_data(stream, server_info, query, packet, len); goto out; } else if (server_info->ssl && errno != ENOMEM) { _dns_client_shutdown_socket(server_info); } ret = -1; goto out; } else if (send_len < len) { /* save remain data to buffer, and retry when EPOLLOUT is available */ ret = _dns_client_quic_pending_data(stream, server_info, query, packet + send_len, len - send_len); goto out; } out: if (stream) { _dns_client_conn_stream_put(stream); } return ret; } #endif int _dns_client_send_quic(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet, unsigned short len) { #if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC) unsigned char inpacket_data[DNS_IN_PACKSIZE]; unsigned char *inpacket = inpacket_data; if (len > sizeof(inpacket_data) - 2) { tlog(TLOG_ERROR, "packet size is invalid."); return -1; } /* TCP query format * | len (short) | dns query data | */ *((unsigned short *)(inpacket)) = htons(len); memcpy(inpacket + 2, packet, len); len += 2; /* set query id to zero */ memset(inpacket + 2, 0, 2); return _dns_client_send_quic_data(query, server_info, inpacket, len); #else tlog(TLOG_ERROR, "quic is not supported."); #endif return 0; } ================================================ FILE: src/dns_client/client_quic.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_QUIC_H_ #define _DNS_CLIENT_QUIC_H_ #include "dns_client.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_create_socket_quic(struct dns_server_info *server_info, const char *hostname, const char *alpn); int _dns_client_send_quic(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet, unsigned short len); int _dns_client_send_quic_data(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet, unsigned short len); int _dns_client_process_quic(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/client_socket.c ================================================ /************************************************************************* * * 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 . */ #include "client_socket.h" #include "client_http3.h" #include "client_https.h" #include "client_mdns.h" #include "client_quic.h" #include "client_tcp.h" #include "client_tls.h" #include "client_udp.h" #include "conn_stream.h" #include #include int _dns_client_create_socket(struct dns_server_info *server_info) { int ret = -1; pthread_mutex_lock(&server_info->lock); if (server_info->fd > 0) { pthread_mutex_unlock(&server_info->lock); return -1; } time(&server_info->last_send); time(&server_info->last_recv); if (server_info->type == DNS_SERVER_UDP) { ret = _dns_client_create_socket_udp(server_info); } else if (server_info->type == DNS_SERVER_MDNS) { ret = _dns_client_create_socket_udp_mdns(server_info); } else if (server_info->type == DNS_SERVER_TCP) { ret = _dns_client_create_socket_tcp(server_info); } else if (server_info->type == DNS_SERVER_TLS) { struct client_dns_server_flag_tls *flag_tls = NULL; flag_tls = &server_info->flags.tls; ret = _dns_client_create_socket_tls(server_info, flag_tls->hostname, flag_tls->alpn); } else if (server_info->type == DNS_SERVER_QUIC) { struct client_dns_server_flag_tls *flag_tls = NULL; const char *alpn = "doq"; flag_tls = &server_info->flags.tls; if (flag_tls->alpn[0] != 0) { alpn = flag_tls->alpn; } ret = _dns_client_create_socket_quic(server_info, flag_tls->hostname, alpn); } else if (server_info->type == DNS_SERVER_HTTPS) { struct client_dns_server_flag_https *flag_https = NULL; const char *alpn = "h2,http/1.1"; flag_https = &server_info->flags.https; if (flag_https->alpn[0] != 0) { alpn = flag_https->alpn; } ret = _dns_client_create_socket_tls(server_info, flag_https->hostname, alpn); } else if (server_info->type == DNS_SERVER_HTTP3) { struct client_dns_server_flag_https *flag_https = NULL; const char *alpn = "h3"; flag_https = &server_info->flags.https; if (flag_https->alpn[0] != 0) { alpn = flag_https->alpn; } ret = _dns_client_create_socket_quic(server_info, flag_https->hostname, alpn); } else { ret = -1; } pthread_mutex_unlock(&server_info->lock); return ret; } void _dns_client_close_socket_ext(struct dns_server_info *server_info, int no_del_conn_list) { dns_server_status server_status = DNS_SERVER_STATUS_DISCONNECTED; pthread_mutex_lock(&server_info->lock); server_status = server_info->status; server_info->status = DNS_SERVER_STATUS_DISCONNECTED; if (server_info->ssl) { /* Shutdown ssl */ if (server_status == DNS_SERVER_STATUS_CONNECTED) { _ssl_shutdown(server_info); } if (server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) { struct dns_conn_stream *conn_stream = NULL; struct dns_conn_stream *tmp = NULL; list_for_each_entry_safe(conn_stream, tmp, &server_info->conn_stream_list, server_list) { if (conn_stream->quic_stream) { #if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC) SSL_stream_reset(conn_stream->quic_stream, NULL, 0); #endif SSL_free(conn_stream->quic_stream); conn_stream->quic_stream = NULL; } if (no_del_conn_list == 1) { continue; } conn_stream->server_info = NULL; list_del_init(&conn_stream->server_list); _dns_client_conn_stream_put(conn_stream); } } else if (server_info->type == DNS_SERVER_HTTPS) { struct dns_conn_stream *conn_stream = NULL; struct dns_conn_stream *tmp = NULL; list_for_each_entry_safe(conn_stream, tmp, &server_info->conn_stream_list, server_list) { if (conn_stream->http2_stream) { http2_stream_close(conn_stream->http2_stream); conn_stream->http2_stream = NULL; } if (no_del_conn_list == 1) { continue; } conn_stream->server_info = NULL; list_del_init(&conn_stream->server_list); _dns_client_conn_stream_put(conn_stream); } } SSL_free(server_info->ssl); server_info->ssl = NULL; server_info->ssl_write_len = -1; } /* Clean up HTTP/2 context (connection-level) */ if (server_info->http2_ctx) { http2_ctx_put(server_info->http2_ctx); server_info->http2_ctx = NULL; } if (server_info->bio_method) { BIO_meth_free(server_info->bio_method); server_info->bio_method = NULL; } if (server_info->proxy) { proxy_conn_free(server_info->proxy); server_info->proxy = NULL; } else if (server_info->fd > 0) { close(server_info->fd); } if (server_info->fd > 0) { epoll_ctl(client.epoll_fd, EPOLL_CTL_DEL, server_info->fd, NULL); } server_info->fd = -1; tlog(TLOG_DEBUG, "server %s:%d closed.", server_info->ip, server_info->port); /* update send recv time */ time(&server_info->last_send); time(&server_info->last_recv); pthread_mutex_unlock(&server_info->lock); } void _dns_client_close_socket(struct dns_server_info *server_info) { _dns_client_close_socket_ext(server_info, 0); } void _dns_client_shutdown_socket(struct dns_server_info *server_info) { if (server_info->fd <= 0) { return; } switch (server_info->type) { case DNS_SERVER_UDP: server_info->status = DNS_SERVER_STATUS_CONNECTING; atomic_set(&server_info->is_alive, 0); return; break; case DNS_SERVER_TCP: if (server_info->fd > 0) { shutdown(server_info->fd, SHUT_RDWR); } break; case DNS_SERVER_QUIC: case DNS_SERVER_TLS: case DNS_SERVER_HTTP3: case DNS_SERVER_HTTPS: if (server_info->ssl) { /* Shutdown ssl */ if (server_info->status == DNS_SERVER_STATUS_CONNECTED) { _ssl_shutdown(server_info); } shutdown(server_info->fd, SHUT_RDWR); } atomic_set(&server_info->is_alive, 0); break; case DNS_SERVER_MDNS: break; default: break; } } int _dns_client_socket_send(struct dns_server_info *server_info) { if (server_info->type == DNS_SERVER_UDP) { return -1; } else if (server_info->type == DNS_SERVER_TCP) { return send(server_info->fd, server_info->send_buff.data, server_info->send_buff.len, MSG_NOSIGNAL); } else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS || server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) { int write_len = server_info->send_buff.len; if (server_info->ssl_write_len > 0) { write_len = server_info->ssl_write_len; server_info->ssl_write_len = -1; } server_info->ssl_want_write = 0; int ret = _dns_client_socket_ssl_send(server_info, server_info->send_buff.data, write_len); if (ret < 0 && errno == EAGAIN) { server_info->ssl_write_len = write_len; if (_dns_client_ssl_poll_event(server_info, SSL_ERROR_WANT_WRITE) == 0) { errno = EAGAIN; } } return ret; } else if (server_info->type == DNS_SERVER_MDNS) { return -1; } else { return -1; } } int _dns_client_socket_recv(struct dns_server_info *server_info) { if (server_info->type == DNS_SERVER_UDP) { return -1; } else if (server_info->type == DNS_SERVER_TCP) { return recv(server_info->fd, server_info->recv_buff.data + server_info->recv_buff.len, DNS_TCP_BUFFER - server_info->recv_buff.len, 0); } else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS || server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) { int ret = _dns_client_socket_ssl_recv(server_info, server_info->recv_buff.data + server_info->recv_buff.len, DNS_TCP_BUFFER - server_info->recv_buff.len); if (ret == -SSL_ERROR_WANT_WRITE && errno == EAGAIN) { if (_dns_client_ssl_poll_event(server_info, SSL_ERROR_WANT_WRITE) == 0) { errno = EAGAIN; server_info->ssl_want_write = 1; } } return ret; } else if (server_info->type == DNS_SERVER_MDNS) { return -1; } else { return -1; } } int _dns_client_copy_data_to_buffer(struct dns_server_info *server_info, void *packet, int len) { if (DNS_TCP_BUFFER - server_info->send_buff.len < len) { errno = ENOMEM; return -1; } memcpy(server_info->send_buff.data + server_info->send_buff.len, packet, len); server_info->send_buff.len += len; return 0; } int _dns_client_send_data_to_buffer(struct dns_server_info *server_info, void *packet, int len) { struct epoll_event event; if (DNS_TCP_BUFFER - server_info->send_buff.len < len) { errno = ENOMEM; return -1; } memcpy(server_info->send_buff.data + server_info->send_buff.len, packet, len); server_info->send_buff.len += len; if (server_info->fd <= 0) { errno = ECONNRESET; return -1; } memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) { if (errno == ENOENT) { /* fd not found, ignore */ return 0; } tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); return -1; } return 0; } ================================================ FILE: src/dns_client/client_socket.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_CLIENT_SOCKET_ #define _DNS_CLIENT_CLIENT_SOCKET_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_send_data_to_buffer(struct dns_server_info *server_info, void *packet, int len); int _dns_client_copy_data_to_buffer(struct dns_server_info *server_info, void *packet, int len); int _dns_client_socket_send(struct dns_server_info *server_info); int _dns_client_socket_recv(struct dns_server_info *server_info); int _dns_client_create_socket(struct dns_server_info *server_info); void _dns_client_close_socket(struct dns_server_info *server_info); void _dns_client_close_socket_ext(struct dns_server_info *server_info, int no_del_conn_list); void _dns_client_shutdown_socket(struct dns_server_info *server_info); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/client_tcp.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/http_parse.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include "client_https.h" #include "client_socket.h" #include "client_tcp.h" #include "client_tls.h" #include "conn_stream.h" #include "server_info.h" #include #include #include #include #include int _dns_client_create_socket_tcp(struct dns_server_info *server_info) { int fd = -1; struct epoll_event event; int yes = 1; const int priority = SOCKET_PRIORITY; const int ip_tos = SOCKET_IP_TOS; struct proxy_conn *proxy = NULL; int ret = 0; if (server_info->proxy_name[0] != '\0') { proxy = proxy_conn_new(server_info->proxy_name, server_info->ip, server_info->port, 0, 1); if (proxy == NULL) { tlog(TLOG_ERROR, "create proxy failed, %s, proxy: %s", server_info->ip, server_info->proxy_name); goto errout; } fd = proxy_conn_get_fd(proxy); } else { fd = socket(server_info->ai_family, SOCK_STREAM, 0); } if (fd < 0) { tlog(TLOG_ERROR, "create socket failed, %s", strerror(errno)); goto errout; } if (server_info->flags.ifname[0] != '\0') { struct ifreq ifr; memset(&ifr, 0, sizeof(struct ifreq)); safe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name)); ioctl(fd, SIOCGIFINDEX, &ifr); if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) { tlog(TLOG_ERROR, "bind socket to device %s failed, %s\n", ifr.ifr_name, strerror(errno)); goto errout; } } if (set_fd_nonblock(fd, 1) != 0) { tlog(TLOG_ERROR, "set socket non block failed, %s", strerror(errno)); goto errout; } if (server_info->so_mark >= 0) { unsigned int so_mark = server_info->so_mark; if (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) { tlog(TLOG_DEBUG, "set socket mark failed, %s", strerror(errno)); } } /* enable tcp fast open */ if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &yes, sizeof(yes)) != 0) { tlog(TLOG_DEBUG, "enable TCP fast open failed, %s", strerror(errno)); } setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); setsockopt(fd, IPPROTO_TCP, TCP_THIN_DUPACK, &yes, sizeof(yes)); setsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, &yes, sizeof(yes)); set_sock_keepalive(fd, 30, 3, 5); if (dns_conf.dns_socket_buff_size > 0) { setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); } if (proxy) { ret = proxy_conn_connect(proxy); } else { ret = connect(fd, &server_info->addr, server_info->ai_addrlen); } if (ret != 0) { if (errno != EINPROGRESS) { tlog(TLOG_DEBUG, "connect %s failed, %s", server_info->ip, strerror(errno)); goto errout; } } server_info->fd = fd; server_info->status = DNS_SERVER_STATUS_CONNECTING; server_info->security_status = DNS_CLIENT_SERVER_SECURITY_NOT_APPLICABLE; server_info->proxy = proxy; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); return -1; } tlog(TLOG_DEBUG, "tcp server %s connecting.\n", server_info->ip); return 0; errout: if (server_info->fd > 0) { server_info->fd = -1; } server_info->status = DNS_SERVER_STATUS_INIT; server_info->proxy = NULL; server_info->security_status = DNS_CLIENT_SERVER_SECURITY_UNKNOW; server_info->ssl_write_len = -1; if (fd > 0 && proxy == NULL) { close(fd); } if (proxy) { proxy_conn_free(proxy); } return -1; } static int _dns_client_process_tcp_buff(struct dns_server_info *server_info) { int len = 0; int dns_packet_len = 0; struct http_head *http_head = NULL; unsigned char *inpacket_data = NULL; int ret = -1; while (1) { if (server_info->type == DNS_SERVER_HTTPS) { http_head = http_head_init(4096, HTTP_VERSION_1_1); if (http_head == NULL) { goto out; } len = http_head_parse(http_head, server_info->recv_buff.data, server_info->recv_buff.len); if (len < 0) { if (len == -1) { ret = 0; goto out; } else if (len == -3) { /* repsone is too large */ tlog(TLOG_DEBUG, "http response is too large."); server_info->recv_buff.len = 0; goto out; } tlog(TLOG_DEBUG, "remote server not supported."); goto out; } if (http_head_get_httpcode(http_head) != 200) { tlog(TLOG_WARN, "http server query from %s:%d failed, server return http code : %d, %s", server_info->ip, server_info->port, http_head_get_httpcode(http_head), http_head_get_httpcode_msg(http_head)); server_info->prohibit = 1; goto out; } dns_packet_len = http_head_get_data_len(http_head); inpacket_data = (unsigned char *)http_head_get_data(http_head); } else { /* tcp result format * | len (short) | dns query result | */ inpacket_data = server_info->recv_buff.data; len = ntohs(*((unsigned short *)(inpacket_data))); if (len <= 0 || len >= DNS_IN_PACKSIZE) { /* data len is invalid */ goto out; } if (len > server_info->recv_buff.len - 2) { /* len is not expected, wait and recv */ ret = 0; goto out; } inpacket_data = server_info->recv_buff.data + 2; dns_packet_len = len; len += 2; } if (inpacket_data == NULL || dns_packet_len <= 0) { tlog(TLOG_WARN, "recv tcp packet from %s, len = %d", server_info->ip, len); goto out; } tlog(TLOG_DEBUG, "recv tcp packet from %s, len = %d", server_info->ip, len); time(&server_info->last_recv); /* process result */ if (_dns_client_recv(server_info, inpacket_data, dns_packet_len, &server_info->addr, server_info->ai_addrlen) != 0) { goto out; } if (http_head) { http_head_destroy(http_head); http_head = NULL; } server_info->recv_buff.len -= len; if (server_info->recv_buff.len < 0) { BUG("Internal error."); } /* move to next result */ if (server_info->recv_buff.len > 0) { memmove(server_info->recv_buff.data, server_info->recv_buff.data + len, server_info->recv_buff.len); } else { ret = 0; goto out; } } ret = 0; out: if (http_head) { http_head_destroy(http_head); } return ret; } static int _dns_client_process_https_streams(struct dns_server_info *server_info) { struct dns_conn_stream *stream, *tmp; int ret = 0; /* If negotiated HTTP/2, skip HTTP/1.1 processing */ if (server_info->alpn_selected[0] != '\0' && strncmp(server_info->alpn_selected, "h2", 2) == 0) { return 0; } pthread_mutex_lock(&server_info->lock); list_for_each_entry_safe(stream, tmp, &server_info->conn_stream_list, server_list) { if (stream->send_buff.len == 0) { continue; } /* Format raw DNS data to HTTP/1.1 */ unsigned char http_packet[DNS_IN_PACKSIZE]; int http_len = _dns_client_format_https_packet(server_info, stream->send_buff.data, stream->send_buff.len, http_packet, sizeof(http_packet)); if (http_len < 0) { goto errout; } /* Send the formatted packet */ int send_len; if (server_info->ssl) { send_len = _dns_client_socket_ssl_send(server_info, http_packet, http_len); } else { send_len = send(server_info->fd, http_packet, http_len, MSG_NOSIGNAL); } if (send_len < 0) { if (errno == EAGAIN) { /* Buffer the formatted data */ if (_dns_client_send_data_to_buffer(server_info, http_packet, http_len) != 0) { goto errout; } goto out; } goto errout; } if (send_len < http_len) { /* Buffer remaining */ if (_dns_client_send_data_to_buffer(server_info, http_packet + send_len, http_len - send_len) != 0) { goto errout; } goto out; } /* Clear stream buffer after sending */ stream->send_buff.len = 0; /* Remove from list and release */ list_del_init(&stream->server_list); _dns_client_conn_stream_put(stream); } out: pthread_mutex_unlock(&server_info->lock); return ret; errout: ret = -1; goto out; } int _dns_client_process_tcp(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) { int len = 0; int ret = -1; if (event->events & EPOLLIN) { /* receive from tcp */ len = _dns_client_socket_recv(server_info); if (len < 0) { /* no data to recv, try again */ if (errno == EAGAIN || errno == EWOULDBLOCK) { return 0; } if (errno == ECONNRESET || errno == ENETUNREACH || errno == EHOSTUNREACH) { tlog(TLOG_DEBUG, "recv failed, server %s:%d, %s\n", server_info->ip, server_info->port, strerror(errno)); goto errout; } if (errno == ETIMEDOUT || errno == ECONNREFUSED) { tlog(TLOG_INFO, "recv failed, server %s:%d, %s\n", server_info->ip, server_info->port, strerror(errno)); goto errout; } tlog(TLOG_WARN, "recv failed, server %s:%d, %s\n", server_info->ip, server_info->port, strerror(errno)); goto errout; } /* peer server close */ if (len == 0) { pthread_mutex_lock(&client.server_list_lock); _dns_client_close_socket(server_info); server_info->recv_buff.len = 0; if (server_info->send_buff.len > 0) { /* still remain request data, reconnect and send*/ ret = _dns_client_create_socket(server_info); } else { ret = 0; } pthread_mutex_unlock(&client.server_list_lock); tlog(TLOG_DEBUG, "peer close, %s:%d", server_info->ip, server_info->port); return ret; } server_info->recv_buff.len += len; if (server_info->recv_buff.len <= 2) { /* wait and recv */ return 0; } if (_dns_client_process_tcp_buff(server_info) != 0) { goto errout; } } /* when connected */ if (event->events & EPOLLOUT) { if (server_info->status == DNS_SERVER_STATUS_CONNECTING) { server_info->status = DNS_SERVER_STATUS_CONNECTED; tlog(TLOG_DEBUG, "tcp server %s connected", server_info->ip); } if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { server_info->status = DNS_SERVER_STATUS_DISCONNECTED; } if (server_info->send_buff.len > 0 || server_info->ssl_want_write == 1) { /* send existing send_buffer data */ len = _dns_client_socket_send(server_info); if (len < 0) { if (errno == EAGAIN) { return 0; } goto errout; } pthread_mutex_lock(&client.server_list_lock); server_info->send_buff.len -= len; if (server_info->send_buff.len > 0) { memmove(server_info->send_buff.data, server_info->send_buff.data + len, server_info->send_buff.len); } else if (server_info->send_buff.len < 0) { BUG("Internal Error"); } pthread_mutex_unlock(&client.server_list_lock); } /* Process HTTPS streams if any */ if (server_info->type == DNS_SERVER_HTTPS && server_info->send_buff.len == 0) { if (_dns_client_process_https_streams(server_info) != 0) { goto errout; } } /* still remain data, retry */ if (server_info->send_buff.len > 0) { return 0; } /* clear epollout event */ struct epoll_event mod_event; memset(&mod_event, 0, sizeof(mod_event)); mod_event.events = EPOLLIN; mod_event.data.ptr = server_info; if (server_info->fd > 0) { if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &mod_event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); goto errout; } } } return 0; errout: pthread_mutex_lock(&client.server_list_lock); server_info->recv_buff.len = 0; server_info->send_buff.len = 0; _dns_client_close_socket(server_info); pthread_mutex_unlock(&client.server_list_lock); return -1; } int _dns_client_send_tcp(struct dns_server_info *server_info, void *packet, unsigned short len) { int send_len = 0; unsigned char inpacket_data[DNS_IN_PACKSIZE]; unsigned char *inpacket = inpacket_data; if (len > sizeof(inpacket_data) - 2) { tlog(TLOG_ERROR, "packet size is invalid."); return -1; } /* TCP query format * | len (short) | dns query data | */ *((unsigned short *)(inpacket)) = htons(len); memcpy(inpacket + 2, packet, len); len += 2; if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { return _dns_client_send_data_to_buffer(server_info, inpacket, len); } if (server_info->fd <= 0) { return -1; } send_len = send(server_info->fd, inpacket, len, MSG_NOSIGNAL); if (send_len < 0) { if (errno == EAGAIN) { /* save data to buffer, and retry when EPOLLOUT is available */ return _dns_client_send_data_to_buffer(server_info, inpacket, len); } else if (errno == EPIPE) { _dns_client_shutdown_socket(server_info); } return -1; } else if (send_len < len) { /* save remain data to buffer, and retry when EPOLLOUT is available */ return _dns_client_send_data_to_buffer(server_info, inpacket + send_len, len - send_len); } return 0; } void _dns_client_check_tcp(void) { struct dns_server_info *server_info = NULL; time_t now = 0; time(&now); pthread_mutex_lock(&client.server_list_lock); list_for_each_entry(server_info, &client.dns_server_list, list) { if (server_info->type == DNS_SERVER_UDP || server_info->type == DNS_SERVER_MDNS) { /* no need to check udp server */ continue; } #if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC) if (server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) { if (server_info->ssl) { _ssl_do_handevent(server_info); if (SSL_get_shutdown(server_info->ssl) != 0) { _dns_client_close_socket_ext(server_info, 1); tlog(TLOG_DEBUG, "quick server %s:%d shutdown.", server_info->ip, server_info->port); } } } #endif if (server_info->status == DNS_SERVER_STATUS_CONNECTING) { if (server_info->last_recv + DNS_TCP_CONNECT_TIMEOUT < now) { tlog(TLOG_DEBUG, "server %s:%d connect timeout.", server_info->ip, server_info->port); _dns_client_close_socket(server_info); } } else if (server_info->status == DNS_SERVER_STATUS_CONNECTED) { if (server_info->last_recv + DNS_TCP_IDLE_TIMEOUT < now) { /*disconnect if the server is not responding */ server_info->recv_buff.len = 0; server_info->send_buff.len = 0; _dns_client_close_socket(server_info); } } } pthread_mutex_unlock(&client.server_list_lock); } ================================================ FILE: src/dns_client/client_tcp.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_TCP_H_ #define _DNS_CLIENT_TCP_H_ #include "dns_client.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_create_socket_tcp(struct dns_server_info *server_info); int _dns_client_process_tcp(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now); int _dns_client_send_tcp(struct dns_server_info *server_info, void *packet, unsigned short len); void _dns_client_check_tcp(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/client_tls.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "client_tls.h" #include "client_http2.h" #include "client_quic.h" #include "client_socket.h" #include "client_tcp.h" #include "conn_stream.h" #include "server_info.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include #include #include #include #include #include #include #include #include static ssize_t _ssl_read_ext(struct dns_server_info *server, SSL *ssl, void *buff, int num) { ssize_t ret = 0; pthread_mutex_lock(&server->lock); if (server == NULL || buff == NULL || ssl == NULL) { pthread_mutex_unlock(&server->lock); return SSL_ERROR_SYSCALL; } ret = SSL_read(ssl, buff, num); pthread_mutex_unlock(&server->lock); return ret; } static ssize_t _ssl_write_ext2(struct dns_server_info *server, SSL *ssl, const void *buff, int num, uint64_t flags) { ssize_t ret = 0; size_t written = 0; pthread_mutex_lock(&server->lock); if (server == NULL || buff == NULL || ssl == NULL) { pthread_mutex_unlock(&server->lock); return SSL_ERROR_SYSCALL; } #if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC) ret = SSL_write_ex2(ssl, buff, num, flags, &written); #elif OPENSSL_VERSION_NUMBER >= 0x10101000L ret = SSL_write_ex(ssl, buff, num, &written); #else ret = SSL_write(ssl, buff, num); written = ret; #endif pthread_mutex_unlock(&server->lock); if (ret <= 0) { return ret; } return written; } int _ssl_shutdown(struct dns_server_info *server) { int ret = 0; pthread_mutex_lock(&server->lock); if (server == NULL || server->ssl == NULL) { pthread_mutex_unlock(&server->lock); return SSL_ERROR_SYSCALL; } ret = SSL_shutdown(server->ssl); pthread_mutex_unlock(&server->lock); return ret; } static int _ssl_get_error_ext(struct dns_server_info *server, SSL *ssl, int ret) { int err = 0; pthread_mutex_lock(&server->lock); if (server == NULL || ssl == NULL) { pthread_mutex_unlock(&server->lock); return SSL_ERROR_SYSCALL; } err = SSL_get_error(ssl, ret); pthread_mutex_unlock(&server->lock); return err; } static int _ssl_get_error(struct dns_server_info *server, int ret) { return _ssl_get_error_ext(server, server->ssl, ret); } static int _ssl_do_handshake(struct dns_server_info *server) { int err = 0; pthread_mutex_lock(&server->lock); if (server == NULL || server->ssl == NULL) { pthread_mutex_unlock(&server->lock); return SSL_ERROR_SYSCALL; } err = SSL_do_handshake(server->ssl); pthread_mutex_unlock(&server->lock); return err; } int _ssl_do_handevent(struct dns_server_info *server) { int err = 0; pthread_mutex_lock(&server->lock); if (server == NULL || server->ssl == NULL) { pthread_mutex_unlock(&server->lock); return SSL_ERROR_SYSCALL; } #if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC) err = SSL_handle_events(server->ssl); #else err = SSL_ERROR_SYSCALL; #endif pthread_mutex_unlock(&server->lock); return err; } static int _ssl_session_reused(struct dns_server_info *server) { int err = 0; pthread_mutex_lock(&server->lock); if (server == NULL || server->ssl == NULL) { pthread_mutex_unlock(&server->lock); return SSL_ERROR_SYSCALL; } err = SSL_session_reused(server->ssl); pthread_mutex_unlock(&server->lock); return err; } static SSL_SESSION *_ssl_get1_session(struct dns_server_info *server) { SSL_SESSION *ret = NULL; pthread_mutex_lock(&server->lock); if (server == NULL || server->ssl == NULL) { pthread_mutex_unlock(&server->lock); return NULL; } ret = SSL_get1_session(server->ssl); pthread_mutex_unlock(&server->lock); return ret; } int dns_client_spki_decode(const char *spki, unsigned char *spki_data_out, int spki_data_out_max_len) { int spki_data_len = -1; spki_data_len = SSL_base64_decode(spki, spki_data_out, spki_data_out_max_len); if (spki_data_len != SHA256_DIGEST_LENGTH) { return -1; } return spki_data_len; } static char *_dns_client_server_get_tls_host_verify(struct dns_server_info *server_info) { char *tls_host_verify = NULL; switch (server_info->type) { case DNS_SERVER_UDP: { } break; case DNS_SERVER_HTTP3: case DNS_SERVER_HTTPS: { struct client_dns_server_flag_https *flag_https = &server_info->flags.https; tls_host_verify = flag_https->tls_host_verify; } break; case DNS_SERVER_QUIC: case DNS_SERVER_TLS: { struct client_dns_server_flag_tls *flag_tls = &server_info->flags.tls; tls_host_verify = flag_tls->tls_host_verify; } break; case DNS_SERVER_TCP: break; case DNS_SERVER_MDNS: break; default: return NULL; break; } if (tls_host_verify) { if (tls_host_verify[0] == '\0') { return NULL; } } return tls_host_verify; } static char *_dns_client_server_get_spki(struct dns_server_info *server_info, int *spki_len) { *spki_len = 0; char *spki = NULL; switch (server_info->type) { case DNS_SERVER_UDP: { } break; case DNS_SERVER_HTTP3: case DNS_SERVER_HTTPS: { struct client_dns_server_flag_https *flag_https = &server_info->flags.https; spki = flag_https->spki; *spki_len = flag_https->spi_len; } break; case DNS_SERVER_QUIC: case DNS_SERVER_TLS: { struct client_dns_server_flag_tls *flag_tls = &server_info->flags.tls; spki = flag_tls->spki; *spki_len = flag_tls->spi_len; } break; case DNS_SERVER_TCP: break; case DNS_SERVER_MDNS: break; default: return NULL; break; } if (*spki_len <= 0) { return NULL; } return spki; } static int _dns_client_set_trusted_cert(SSL_CTX *ssl_ctx) { char *cafile = NULL; char *capath = NULL; int cert_path_set = 0; if (ssl_ctx == NULL) { return -1; } if (dns_conf.ca_file[0]) { cafile = dns_conf.ca_file; } if (dns_conf.ca_path[0]) { capath = dns_conf.ca_path; } if (cafile == NULL && capath == NULL) { if (SSL_CTX_set_default_verify_paths(ssl_ctx)) { cert_path_set = 1; } const STACK_OF(X509_NAME) *cas = SSL_CTX_get_client_CA_list(ssl_ctx); if (cas && sk_X509_NAME_num(cas) == 0) { cafile = "/etc/ssl/certs/ca-certificates.crt"; capath = "/etc/ssl/certs"; cert_path_set = 0; } } if (cert_path_set == 0) { if (SSL_CTX_load_verify_locations(ssl_ctx, cafile, capath) == 0) { tlog(TLOG_WARN, "load certificate from %s:%s failed.", cafile, capath); return -1; } } return 0; } SSL_CTX *_ssl_ctx_get(int is_quic) { SSL_CTX **ssl_ctx = NULL; pthread_mutex_lock(&client.server_list_lock); if (is_quic) { ssl_ctx = &client.ssl_quic_ctx; } else { ssl_ctx = &client.ssl_ctx; } if (*ssl_ctx) { pthread_mutex_unlock(&client.server_list_lock); return *ssl_ctx; } #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) #if (OPENSSL_VERSION_NUMBER >= 0x30200000L) if (is_quic) { *ssl_ctx = SSL_CTX_new(OSSL_QUIC_client_method()); } else { *ssl_ctx = SSL_CTX_new(TLS_client_method()); } #else if (is_quic) { return NULL; } *ssl_ctx = SSL_CTX_new(TLS_client_method()); #endif #else *ssl_ctx = SSL_CTX_new(SSLv23_client_method()); #endif if (*ssl_ctx == NULL) { tlog(TLOG_ERROR, "init ssl failed."); goto errout; } SSL_CTX_set_options(*ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); SSL_CTX_set_session_cache_mode(*ssl_ctx, SSL_SESS_CACHE_CLIENT); SSL_CTX_sess_set_cache_size(*ssl_ctx, DNS_MAX_SERVERS); if (_dns_client_set_trusted_cert(*ssl_ctx) != 0) { SSL_CTX_set_verify(*ssl_ctx, SSL_VERIFY_NONE, NULL); client.ssl_verify_skip = 1; } pthread_mutex_unlock(&client.server_list_lock); return *ssl_ctx; errout: if (*ssl_ctx) { SSL_CTX_free(*ssl_ctx); } *ssl_ctx = NULL; pthread_mutex_unlock(&client.server_list_lock); return NULL; } int _dns_client_create_socket_tls(struct dns_server_info *server_info, const char *hostname, const char *alpn) { int fd = -1; struct epoll_event event; SSL *ssl = NULL; struct proxy_conn *proxy = NULL; int yes = 1; const int priority = SOCKET_PRIORITY; const int ip_tos = SOCKET_IP_TOS; int ret = -1; if (server_info->ssl_ctx == NULL) { tlog(TLOG_ERROR, "create ssl ctx failed, %s", server_info->ip); goto errout; } if (server_info->proxy_name[0] != '\0') { proxy = proxy_conn_new(server_info->proxy_name, server_info->ip, server_info->port, 0, 1); if (proxy == NULL) { tlog(TLOG_ERROR, "create proxy failed, %s, proxy: %s", server_info->ip, server_info->proxy_name); goto errout; } fd = proxy_conn_get_fd(proxy); } else { fd = socket(server_info->ai_family, SOCK_STREAM, 0); } if (fd < 0) { tlog(TLOG_ERROR, "create socket failed, %s", strerror(errno)); goto errout; } if (server_info->flags.ifname[0] != '\0') { struct ifreq ifr; memset(&ifr, 0, sizeof(struct ifreq)); safe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name)); ioctl(fd, SIOCGIFINDEX, &ifr); if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) { tlog(TLOG_ERROR, "bind socket to device %s failed, %s\n", ifr.ifr_name, strerror(errno)); goto errout; } } ssl = SSL_new(server_info->ssl_ctx); if (ssl == NULL) { tlog(TLOG_ERROR, "new ssl failed, %s", server_info->ip); goto errout; } if (set_fd_nonblock(fd, 1) != 0) { tlog(TLOG_ERROR, "set socket non block failed, %s", strerror(errno)); goto errout; } if (server_info->so_mark >= 0) { unsigned int so_mark = server_info->so_mark; if (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) { tlog(TLOG_DEBUG, "set socket mark failed, %s", strerror(errno)); } } if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &yes, sizeof(yes)) != 0) { tlog(TLOG_DEBUG, "enable TCP fast open failed."); } // ? this cause ssl crash ? setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); setsockopt(fd, IPPROTO_TCP, TCP_THIN_DUPACK, &yes, sizeof(yes)); setsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, &yes, sizeof(yes)); set_sock_keepalive(fd, 30, 3, 5); setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); if (dns_conf.dns_socket_buff_size > 0) { setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); } if (proxy) { ret = proxy_conn_connect(proxy); } else { ret = connect(fd, &server_info->addr, server_info->ai_addrlen); } if (ret != 0) { if (errno != EINPROGRESS) { tlog(TLOG_DEBUG, "connect %s failed, %s", server_info->ip, strerror(errno)); goto errout; } } SSL_set_connect_state(ssl); if (SSL_set_fd(ssl, fd) == 0) { tlog(TLOG_ERROR, "ssl set fd failed."); goto errout; } /* reuse ssl session */ if (server_info->ssl_session) { SSL_set_session(ssl, server_info->ssl_session); } SSL_set_mode(ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE); if (hostname && hostname[0] != 0) { SSL_set_tlsext_host_name(ssl, hostname); } if (alpn && alpn[0] != 0) { uint8_t alpn_data[DNS_MAX_ALPN_LEN]; int alpn_data_len = encode_alpn_protos(alpn, alpn_data, sizeof(alpn_data)); if (alpn_data_len > 0) { if (SSL_set_alpn_protos(ssl, alpn_data, alpn_data_len)) { tlog(TLOG_INFO, "SSL_set_alpn_protos failed."); goto errout; } } } if (server_info->ssl) { SSL_free(server_info->ssl); server_info->ssl = NULL; } server_info->fd = fd; server_info->ssl = ssl; server_info->ssl_write_len = -1; server_info->status = DNS_SERVER_STATUS_CONNECTING; server_info->proxy = proxy; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); goto errout; } tlog(TLOG_DEBUG, "tls server %s connecting.\n", server_info->ip); return 0; errout: if (server_info->fd > 0) { server_info->fd = -1; } if (server_info->ssl) { server_info->ssl = NULL; } server_info->status = DNS_SERVER_STATUS_INIT; server_info->proxy = NULL; server_info->ssl_write_len = -1; if (fd > 0 && proxy == NULL) { close(fd); } if (ssl) { SSL_free(ssl); } if (proxy) { proxy_conn_free(proxy); } return -1; } int _dns_client_socket_ssl_send_ext(struct dns_server_info *server, SSL *ssl, const void *buf, int num, uint64_t flags) { int ret = 0; int ssl_ret = 0; unsigned long ssl_err = 0; if (ssl == NULL) { errno = EINVAL; return -1; } if (num < 0) { errno = EINVAL; return -1; } ret = _ssl_write_ext2(server, ssl, buf, num, flags); if (ret > 0) { return ret; } ssl_ret = _ssl_get_error_ext(server, ssl, ret); switch (ssl_ret) { case SSL_ERROR_NONE: case SSL_ERROR_ZERO_RETURN: return 0; break; case SSL_ERROR_WANT_READ: errno = EAGAIN; ret = -SSL_ERROR_WANT_READ; break; case SSL_ERROR_WANT_WRITE: errno = EAGAIN; ret = -SSL_ERROR_WANT_WRITE; break; case SSL_ERROR_SSL: { char buff[256]; ssl_err = ERR_get_error(); int ssl_reason = ERR_GET_REASON(ssl_err); if (ssl_reason == SSL_R_UNINITIALIZED || ssl_reason == SSL_R_PROTOCOL_IS_SHUTDOWN || ssl_reason == SSL_R_BAD_LENGTH || ssl_reason == SSL_R_SHUTDOWN_WHILE_IN_INIT || ssl_reason == SSL_R_BAD_WRITE_RETRY) { errno = EAGAIN; return -1; } tlog(TLOG_WARN, "server %s SSL write fail error: %s", server->ip, ERR_error_string(ssl_err, buff)); errno = EFAULT; ret = -1; } break; case SSL_ERROR_SYSCALL: tlog(TLOG_DEBUG, "SSL syscall failed, %s", strerror(errno)); return ret; default: errno = EFAULT; ret = -1; break; } return ret; } int _dns_client_socket_ssl_recv_ext(struct dns_server_info *server, SSL *ssl, void *buf, int num) { ssize_t ret = 0; int ssl_ret = 0; unsigned long ssl_err = 0; if (ssl == NULL) { errno = EFAULT; return -1; } ret = _ssl_read_ext(server, ssl, buf, num); if (ret > 0) { return ret; } ssl_ret = _ssl_get_error_ext(server, ssl, ret); switch (ssl_ret) { case SSL_ERROR_NONE: case SSL_ERROR_ZERO_RETURN: return 0; break; case SSL_ERROR_WANT_READ: errno = EAGAIN; ret = -SSL_ERROR_WANT_READ; break; case SSL_ERROR_WANT_WRITE: errno = EAGAIN; ret = -SSL_ERROR_WANT_WRITE; break; case SSL_ERROR_SSL: { char buff[256]; ssl_err = ERR_get_error(); int ssl_reason = ERR_GET_REASON(ssl_err); switch (ssl_reason) { case SSL_R_UNINITIALIZED: errno = EAGAIN; return -1; case SSL_R_SHUTDOWN_WHILE_IN_INIT: case SSL_R_PROTOCOL_IS_SHUTDOWN: #ifdef SSL_R_STREAM_FINISHED case SSL_R_STREAM_FINISHED: #endif #ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING case SSL_R_UNEXPECTED_EOF_WHILE_READING: #endif return 0; default: break; } tlog(TLOG_WARN, "server %s SSL read fail error: %s", server->ip, ERR_error_string(ssl_err, buff)); errno = EFAULT; ret = -1; } break; case SSL_ERROR_SYSCALL: if (errno == 0) { return 0; } ret = -1; return ret; default: errno = EFAULT; ret = -1; break; } return ret; } int _dns_client_socket_ssl_send(struct dns_server_info *server, const void *buf, int num) { return _dns_client_socket_ssl_send_ext(server, server->ssl, buf, num, 0); } int _dns_client_socket_ssl_recv(struct dns_server_info *server, void *buf, int num) { return _dns_client_socket_ssl_recv_ext(server, server->ssl, buf, num); } int _dns_client_ssl_poll_event(struct dns_server_info *server_info, int ssl_ret) { struct epoll_event fd_event; memset(&fd_event, 0, sizeof(fd_event)); if (ssl_ret == SSL_ERROR_WANT_READ) { fd_event.events = EPOLLIN; } else if (ssl_ret == SSL_ERROR_WANT_WRITE) { fd_event.events = EPOLLOUT | EPOLLIN; } else { goto errout; } if (server_info->fd < 0) { goto errout; } fd_event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &fd_event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); goto errout; } return 0; errout: return -1; } static inline int _dns_client_to_hex(int c) { if (c > 0x9) { return 'A' + c - 0xA; } return '0' + c; } static int _dns_client_tls_matchName(const char *host, const char *pattern, int size) { int match = -1; int i = 0; int j = 0; while (i < size && host[j] != '\0') { if (toupper(pattern[i]) == toupper(host[j])) { i++; j++; continue; } if (pattern[i] == '*') { while (host[j] != '.' && host[j] != '\0') { j++; } i++; continue; } break; } if (i == size && host[j] == '\0') { match = 0; } return match; } static int _dns_client_tls_get_cert_CN(X509 *cert, char *cn, int max_cn_len) { X509_NAME *cert_name = NULL; cert_name = X509_get_subject_name(cert); if (cert_name == NULL) { tlog(TLOG_ERROR, "get subject name failed."); goto errout; } if (X509_NAME_get_text_by_NID(cert_name, NID_commonName, cn, max_cn_len) == -1) { tlog(TLOG_ERROR, "cannot found x509 name"); goto errout; } return 0; errout: return -1; } /* * check SAN * return 0: match * return -1: not match * return -2: no SAN */ static int _dns_client_verify_SAN(struct dns_server_info *server_info, X509 *cert) { GENERAL_NAMES *alt_names = NULL; int i = 0; int ret = -1; char *tls_host_verify = NULL; /* check tls host */ tls_host_verify = _dns_client_server_get_tls_host_verify(server_info); if (tls_host_verify == NULL) { return 0; } alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); if (alt_names == NULL) { ret = -2; goto errout; } if (sk_GENERAL_NAME_num(alt_names) == 0) { ret = -2; goto errout; } /* found subject alt name */ for (i = 0; i < sk_GENERAL_NAME_num(alt_names); i++) { GENERAL_NAME *name = sk_GENERAL_NAME_value(alt_names, i); if (name == NULL) { continue; } switch (name->type) { case GEN_DNS: { ASN1_IA5STRING *dns = name->d.dNSName; if (dns == NULL) { continue; } tlog(TLOG_DEBUG, "peer SAN: %s", dns->data); if (_dns_client_tls_matchName(tls_host_verify, (char *)dns->data, dns->length) == 0) { tlog(TLOG_DEBUG, "peer SAN match: %s", dns->data); GENERAL_NAMES_free(alt_names); return 0; } } break; case GEN_IPADD: break; default: break; } } tlog(TLOG_WARN, "server %s SAN is invalid, expect SAN: %s", server_info->ip, tls_host_verify); return -1; errout: server_info->prohibit = 1; if (alt_names) { GENERAL_NAMES_free(alt_names); } return ret; } static int _dns_client_verify_common_name(struct dns_server_info *server_info, X509 *cert) { char *tls_host_verify = NULL; char peer_CN[256]; tls_host_verify = _dns_client_server_get_tls_host_verify(server_info); if (tls_host_verify == NULL) { return 0; } if (_dns_client_tls_get_cert_CN(cert, peer_CN, sizeof(peer_CN)) != 0) { tlog(TLOG_ERROR, "get cert CN failed."); goto errout; } tlog(TLOG_DEBUG, "peer CN: %s", peer_CN); /* check tls host */ tls_host_verify = _dns_client_server_get_tls_host_verify(server_info); if (tls_host_verify == NULL) { return 0; } if (tls_host_verify) { if (_dns_client_tls_matchName(tls_host_verify, peer_CN, strnlen(peer_CN, DNS_MAX_CNAME_LEN)) == 0) { return 0; } } errout: tlog(TLOG_WARN, "server %s CN is invalid, expect CN: %s, Peer CN: %s", server_info->ip, tls_host_verify, peer_CN); server_info->prohibit = 1; return -1; } static int _dns_client_tls_verify(struct dns_server_info *server_info) { X509 *cert = NULL; X509_PUBKEY *pubkey = NULL; char cert_fingerprint[256]; int i = 0; int key_len = 0; unsigned char *key_data = NULL; unsigned char *key_data_tmp = NULL; unsigned char *key_sha256 = NULL; char *spki = NULL; int spki_len = 0; int is_secure = 0; if (server_info->ssl == NULL) { return -1; } pthread_mutex_lock(&server_info->lock); cert = SSL_get_peer_certificate(server_info->ssl); if (cert == NULL) { pthread_mutex_unlock(&server_info->lock); tlog(TLOG_ERROR, "get peer certificate failed."); return -1; } if (server_info->skip_check_cert == 0) { long res = SSL_get_verify_result(server_info->ssl); if (res != X509_V_OK) { pthread_mutex_unlock(&server_info->lock); tlog(TLOG_WARN, "peer server %s certificate verify failed, %s", server_info->ip, X509_verify_cert_error_string(res)); goto errout; } is_secure = 1; } pthread_mutex_unlock(&server_info->lock); switch (_dns_client_verify_SAN(server_info, cert)) { case 0: break; case -1: /* verify SAN failed. */ goto errout; case -2: if (_dns_client_verify_common_name(server_info, cert) != 0) { goto errout; } break; default: tlog(TLOG_WARN, "server %s SAN is invalid", server_info->ip); goto errout; } pubkey = X509_get_X509_PUBKEY(cert); if (pubkey == NULL) { tlog(TLOG_ERROR, "get pub key failed."); goto errout; } /* get spki pin */ key_len = i2d_X509_PUBKEY(pubkey, NULL); if (key_len <= 0) { tlog(TLOG_ERROR, "get x509 public key failed."); goto errout; } key_data = OPENSSL_malloc(key_len); key_data_tmp = key_data; if (key_data == NULL) { tlog(TLOG_ERROR, "malloc memory failed."); goto errout; } i2d_X509_PUBKEY(pubkey, &key_data_tmp); /* Get the SHA256 value of SPKI */ key_sha256 = SSL_SHA256(key_data, key_len, NULL); if (key_sha256 == NULL) { tlog(TLOG_ERROR, "get sha256 failed."); goto errout; } char *ptr = cert_fingerprint; for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { *ptr = _dns_client_to_hex(key_sha256[i] >> 4 & 0xF); ptr++; *ptr = _dns_client_to_hex(key_sha256[i] & 0xF); ptr++; *ptr = ':'; ptr++; } ptr--; *ptr = 0; tlog(TLOG_DEBUG, "cert SPKI pin(%s): %s", "sha256", cert_fingerprint); spki = _dns_client_server_get_spki(server_info, &spki_len); if (spki && spki_len > 0 && spki_len <= SHA256_DIGEST_LENGTH) { /* check SPKI */ if (memcmp(spki, key_sha256, spki_len) != 0) { tlog(TLOG_INFO, "server %s cert spki is invalid", server_info->ip); goto errout; } else { tlog(TLOG_DEBUG, "server %s cert spki verify succeed", server_info->ip); is_secure = 1; } } OPENSSL_free(key_data); X509_free(cert); if (is_secure) { server_info->security_status = DNS_CLIENT_SERVER_SECURITY_SECURE; } else { server_info->security_status = DNS_CLIENT_SERVER_SECURITY_INSECURE; } return 0; errout: if (key_data) { OPENSSL_free(key_data); } if (cert) { X509_free(cert); } server_info->security_status = DNS_CLIENT_SERVER_SECURITY_VERIFY_FAILED; return -1; } int _dns_client_process_tls(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) { int ret = -1; struct epoll_event fd_event; int ssl_ret = 0; if (unlikely(server_info->ssl == NULL)) { tlog(TLOG_ERROR, "ssl is invalid, server %s", server_info->ip); goto errout; } if (server_info->status == DNS_SERVER_STATUS_CONNECTING) { /* do SSL handshake */ ret = _ssl_do_handshake(server_info); if (ret <= 0) { memset(&fd_event, 0, sizeof(fd_event)); ssl_ret = _ssl_get_error(server_info, ret); if (_dns_client_ssl_poll_event(server_info, ssl_ret) == 0) { return 0; } if (ssl_ret != SSL_ERROR_SYSCALL) { unsigned long ssl_err = ERR_get_error(); int ssl_reason = ERR_GET_REASON(ssl_err); tlog(TLOG_WARN, "Handshake with %s failed, error no: %s(%d, %d, %d)\n", server_info->ip, ERR_reason_error_string(ssl_err), ret, ssl_ret, ssl_reason); goto errout; } if (errno != ENETUNREACH) { tlog(TLOG_WARN, "Handshake with %s failed, %s", server_info->ip, strerror(errno)); } goto errout; } tlog(TLOG_DEBUG, "remote server %s:%d connected\n", server_info->ip, server_info->port); /* Was the stored session reused? */ if (_ssl_session_reused(server_info)) { tlog(TLOG_DEBUG, "reused session"); } else { tlog(TLOG_DEBUG, "new session"); pthread_mutex_lock(&server_info->lock); if (server_info->ssl_session) { /* free session */ SSL_SESSION_free(server_info->ssl_session); server_info->ssl_session = NULL; } if (_dns_client_tls_verify(server_info) != 0) { tlog(TLOG_WARN, "peer %s verify failed.", server_info->ip); pthread_mutex_unlock(&server_info->lock); goto errout; } /* save ssl session for next request */ server_info->ssl_session = _ssl_get1_session(server_info); pthread_mutex_unlock(&server_info->lock); } /* Detect negotiated ALPN protocol */ const unsigned char *alpn_data = NULL; unsigned int alpn_len = 0; SSL_get0_alpn_selected(server_info->ssl, &alpn_data, &alpn_len); if (alpn_data && alpn_len > 0 && alpn_len < sizeof(server_info->alpn_selected)) { memcpy(server_info->alpn_selected, alpn_data, alpn_len); server_info->alpn_selected[alpn_len] = '\0'; tlog(TLOG_DEBUG, "ALPN negotiated: %s", server_info->alpn_selected); } else { safe_strncpy(server_info->alpn_selected, "http/1.1", sizeof(server_info->alpn_selected)); } server_info->status = DNS_SERVER_STATUS_CONNECTED; memset(&fd_event, 0, sizeof(fd_event)); fd_event.events = EPOLLIN | EPOLLOUT; fd_event.data.ptr = server_info; if (server_info->fd > 0) { if (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &fd_event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); goto errout; } } event->events = EPOLLOUT; } if (server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) { /* QUIC */ #if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC) return _dns_client_process_quic(server_info, event, now); #else tlog(TLOG_ERROR, "quic/http3 is not supported."); goto errout; #endif } /* Check if HTTPS server negotiated HTTP/2 */ if (server_info->type == DNS_SERVER_HTTPS && strncmp(server_info->alpn_selected, "h2", sizeof("h2")) == 0) { /* HTTP/2 processing */ if (_dns_client_process_http2(server_info, event, now) != 0) { goto errout; } return 0; } return _dns_client_process_tcp(server_info, event, now); errout: pthread_mutex_lock(&server_info->lock); server_info->recv_buff.len = 0; server_info->send_buff.len = 0; pthread_mutex_unlock(&server_info->lock); _dns_client_close_socket(server_info); return -1; } int _dns_client_send_tls(struct dns_server_info *server_info, void *packet, unsigned short len) { int send_len = 0; unsigned char inpacket_data[DNS_IN_PACKSIZE]; unsigned char *inpacket = inpacket_data; if (len > sizeof(inpacket_data) - 2) { tlog(TLOG_ERROR, "packet size is invalid."); return -1; } /* TCP query format * | len (short) | dns query data | */ *((unsigned short *)(inpacket)) = htons(len); memcpy(inpacket + 2, packet, len); len += 2; if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { return _dns_client_send_data_to_buffer(server_info, inpacket, len); } if (server_info->ssl == NULL) { errno = EINVAL; return -1; } send_len = _dns_client_socket_ssl_send(server_info, inpacket, len); if (send_len <= 0) { if (errno == EAGAIN || errno == EPIPE || server_info->ssl == NULL) { /* save data to buffer, and retry when EPOLLOUT is available */ return _dns_client_send_data_to_buffer(server_info, inpacket, len); } else if (server_info->ssl && errno != ENOMEM) { _dns_client_shutdown_socket(server_info); } return -1; } else if (send_len < len) { /* save remain data to buffer, and retry when EPOLLOUT is available */ return _dns_client_send_data_to_buffer(server_info, inpacket + send_len, len - send_len); } return 0; } ================================================ FILE: src/dns_client/client_tls.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_TLS_H_ #define _DNS_CLIENT_TLS_H_ #include "dns_client.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_socket_ssl_send(struct dns_server_info *server, const void *buf, int num); int _dns_client_socket_ssl_recv(struct dns_server_info *server, void *buf, int num); int _dns_client_socket_ssl_send_ext(struct dns_server_info *server, SSL *ssl, const void *buf, int num, uint64_t flags); int _dns_client_socket_ssl_recv_ext(struct dns_server_info *server, SSL *ssl, void *buf, int num); int _dns_client_create_socket_tls(struct dns_server_info *server_info, const char *hostname, const char *alpn); int _dns_client_ssl_poll_event(struct dns_server_info *server_info, int ssl_ret); int _dns_client_send_tls(struct dns_server_info *server_info, void *packet, unsigned short len); int _dns_client_process_tls(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now); SSL_CTX *_ssl_ctx_get(int is_quic); int _ssl_shutdown(struct dns_server_info *server); int _ssl_do_handevent(struct dns_server_info *server); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/client_udp.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/util.h" #include "client_socket.h" #include "client_udp.h" #include "server_info.h" #include #include #include #include static int _dns_client_create_socket_udp_proxy(struct dns_server_info *server_info) { struct proxy_conn *proxy = NULL; int fd = -1; struct epoll_event event; int ret = -1; proxy = proxy_conn_new(server_info->proxy_name, server_info->ip, server_info->port, 1, 1); if (proxy == NULL) { tlog(TLOG_ERROR, "create proxy failed, %s, proxy: %s", server_info->ip, server_info->proxy_name); goto errout; } fd = proxy_conn_get_fd(proxy); if (fd < 0) { tlog(TLOG_ERROR, "get proxy fd failed, %s", server_info->ip); goto errout; } if (server_info->so_mark >= 0) { unsigned int so_mark = server_info->so_mark; if (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) { tlog(TLOG_DEBUG, "set socket mark failed, %s", strerror(errno)); } } if (server_info->flags.ifname[0] != '\0') { struct ifreq ifr; memset(&ifr, 0, sizeof(struct ifreq)); safe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name)); ioctl(fd, SIOCGIFINDEX, &ifr); if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) { tlog(TLOG_ERROR, "bind socket to device %s failed, %s\n", ifr.ifr_name, strerror(errno)); goto errout; } } set_fd_nonblock(fd, 1); set_sock_keepalive(fd, 30, 3, 5); if (dns_conf.dns_socket_buff_size > 0) { setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); } ret = proxy_conn_connect(proxy); if (ret != 0) { if (errno != EINPROGRESS) { tlog(TLOG_DEBUG, "connect %s failed, %s", server_info->ip, strerror(errno)); goto errout; } } server_info->fd = fd; server_info->status = DNS_SERVER_STATUS_CONNECTING; server_info->proxy = proxy; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); return -1; } return 0; errout: if (proxy) { proxy_conn_free(proxy); } return -1; } int _dns_client_create_socket_udp(struct dns_server_info *server_info) { int fd = -1; struct epoll_event event; const int on = 1; const int val = 255; const int priority = SOCKET_PRIORITY; const int ip_tos = SOCKET_IP_TOS; if (server_info->proxy_name[0] != '\0') { return _dns_client_create_socket_udp_proxy(server_info); } fd = socket(server_info->ai_family, SOCK_DGRAM, 0); if (fd < 0) { tlog(TLOG_ERROR, "create socket failed, %s", strerror(errno)); goto errout; } if (set_fd_nonblock(fd, 1) != 0) { tlog(TLOG_ERROR, "set socket non block failed, %s", strerror(errno)); goto errout; } if (server_info->flags.ifname[0] != '\0') { struct ifreq ifr; memset(&ifr, 0, sizeof(struct ifreq)); safe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name)); ioctl(fd, SIOCGIFINDEX, &ifr); if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) { tlog(TLOG_ERROR, "bind socket to device %s failed, %s\n", ifr.ifr_name, strerror(errno)); goto errout; } } server_info->fd = fd; server_info->status = DNS_SERVER_STATUS_CONNECTING; server_info->security_status = DNS_CLIENT_SERVER_SECURITY_NOT_APPLICABLE; if (connect(fd, &server_info->addr, server_info->ai_addrlen) != 0) { if (errno != EINPROGRESS) { tlog(TLOG_DEBUG, "connect %s failed, %s", server_info->ip, strerror(errno)); goto errout; } } memset(&event, 0, sizeof(event)); event.events = EPOLLIN; event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed."); return -1; } if (server_info->so_mark >= 0) { unsigned int so_mark = server_info->so_mark; if (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) { tlog(TLOG_DEBUG, "set socket mark failed, %s", strerror(errno)); } } setsockopt(server_info->fd, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on)); setsockopt(server_info->fd, SOL_IP, IP_TTL, &val, sizeof(val)); setsockopt(server_info->fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); setsockopt(server_info->fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); if (server_info->ai_family == AF_INET6) { /* for receiving ip ttl value */ setsockopt(server_info->fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)); setsockopt(server_info->fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on)); setsockopt(server_info->fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on)); } if (dns_conf.dns_socket_buff_size > 0) { setsockopt(server_info->fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); setsockopt(server_info->fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); } return 0; errout: if (fd > 0) { close(fd); } server_info->fd = -1; server_info->status = DNS_SERVER_STATUS_DISCONNECTED; return -1; } static int _dns_client_process_send_udp_buffer(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) { int send_len = 0; if (server_info->send_buff.len <= 0 || server_info->status != DNS_SERVER_STATUS_CONNECTED) { return 0; } while (server_info->send_buff.len - send_len > 0) { int ret = 0; int packet_len = 0; packet_len = *(int *)(server_info->send_buff.data + send_len); send_len += sizeof(packet_len); if (packet_len > server_info->send_buff.len - 1) { goto errout; } ret = _dns_client_send_udp(server_info, server_info->send_buff.data + send_len, packet_len); if (ret < 0) { tlog(TLOG_ERROR, "sendto failed, %s", strerror(errno)); goto errout; } send_len += packet_len; } server_info->send_buff.len -= send_len; if (server_info->send_buff.len < 0) { server_info->send_buff.len = 0; } return 0; errout: pthread_mutex_lock(&client.server_list_lock); server_info->recv_buff.len = 0; server_info->send_buff.len = 0; _dns_client_close_socket(server_info); pthread_mutex_unlock(&client.server_list_lock); return -1; } static int _dns_client_process_udp_proxy(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) { struct sockaddr_storage from; socklen_t from_len = sizeof(from); char from_host[DNS_MAX_CNAME_LEN]; unsigned char inpacket[DNS_IN_PACKSIZE]; int len = 0; int ret = 0; _dns_client_process_send_udp_buffer(server_info, event, now); if (!(event->events & EPOLLIN)) { return 0; } len = proxy_conn_recvfrom(server_info->proxy, inpacket, sizeof(inpacket), 0, (struct sockaddr *)&from, &from_len); if (len < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return 0; } if (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EHOSTUNREACH) { tlog(TLOG_DEBUG, "recvfrom %s failed, %s\n", server_info->ip, strerror(errno)); goto errout; } tlog(TLOG_ERROR, "recvfrom %s failed, %s\n", server_info->ip, strerror(errno)); goto errout; } else if (len == 0) { pthread_mutex_lock(&server_info->lock); _dns_client_close_socket(server_info); server_info->recv_buff.len = 0; if (server_info->send_buff.len > 0) { /* still remain request data, reconnect and send*/ ret = _dns_client_create_socket(server_info); } else { ret = 0; } pthread_mutex_unlock(&server_info->lock); tlog(TLOG_DEBUG, "peer close, %s:%d", server_info->ip, server_info->port); return ret; } int latency = get_tick_count() - server_info->send_tick; tlog(TLOG_DEBUG, "recv udp packet from %s, len: %d, latency: %d", get_host_by_addr(from_host, sizeof(from_host), (struct sockaddr *)&from), len, latency); if (latency < server_info->drop_packet_latency_ms) { tlog(TLOG_DEBUG, "drop packet from %s, latency: %d", from_host, latency); return 0; } if (server_info->status == DNS_SERVER_STATUS_CONNECTING) { server_info->status = DNS_SERVER_STATUS_CONNECTED; } /* update recv time */ time(&server_info->last_recv); /* processing dns packet */ if (_dns_client_recv(server_info, inpacket, len, (struct sockaddr *)&from, from_len) != 0) { return -1; } return 0; errout: pthread_mutex_lock(&server_info->lock); server_info->recv_buff.len = 0; server_info->send_buff.len = 0; _dns_client_close_socket(server_info); pthread_mutex_unlock(&server_info->lock); return -1; } int _dns_client_process_udp(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) { int len = 0; unsigned char inpacket[DNS_IN_PACKSIZE]; struct sockaddr_storage from; socklen_t from_len = sizeof(from); char from_host[DNS_MAX_CNAME_LEN]; struct msghdr msg; struct iovec iov; char ans_data[4096]; int ttl = 0; struct cmsghdr *cmsg = NULL; if (server_info->proxy) { return _dns_client_process_udp_proxy(server_info, event, now); } memset(&msg, 0, sizeof(msg)); iov.iov_base = (char *)inpacket; iov.iov_len = sizeof(inpacket); msg.msg_name = &from; msg.msg_namelen = sizeof(from); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = ans_data; msg.msg_controllen = sizeof(ans_data); len = recvmsg(server_info->fd, &msg, MSG_DONTWAIT); if (len < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return 0; } server_info->prohibit = 1; if (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EHOSTUNREACH) { tlog(TLOG_DEBUG, "recvfrom %s failed, %s\n", server_info->ip, strerror(errno)); goto errout; } tlog(TLOG_ERROR, "recvfrom %s failed, %s\n", server_info->ip, strerror(errno)); goto errout; } from_len = msg.msg_namelen; /* Get the TTL of the IP header */ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_TTL) { if (cmsg->cmsg_len >= sizeof(int)) { int *ttlPtr = (int *)CMSG_DATA(cmsg); ttl = *ttlPtr; } } else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_HOPLIMIT) { if (cmsg->cmsg_len >= sizeof(int)) { int *ttlPtr = (int *)CMSG_DATA(cmsg); ttl = *ttlPtr; } } } int from_port = from.ss_family == AF_INET ? ntohs(((struct sockaddr_in *)&from)->sin_port) : ntohs(((struct sockaddr_in6 *)&from)->sin6_port); int latency = get_tick_count() - server_info->send_tick; tlog(TLOG_DEBUG, "recv udp packet from %s:%d, len: %d, ttl: %d, latency: %d", get_host_by_addr(from_host, sizeof(from_host), (struct sockaddr *)&from), from_port, len, ttl, latency); /* update recv time */ time(&server_info->last_recv); if (latency > 0 && latency < server_info->drop_packet_latency_ms) { tlog(TLOG_DEBUG, "drop packet from %s, latency: %d", from_host, latency); return 0; } if (server_info->status == DNS_SERVER_STATUS_CONNECTING) { server_info->status = DNS_SERVER_STATUS_CONNECTED; } /* processing dns packet */ if (_dns_client_recv(server_info, inpacket, len, (struct sockaddr *)&from, from_len) != 0) { return -1; } return 0; errout: pthread_mutex_lock(&client.server_list_lock); server_info->recv_buff.len = 0; server_info->send_buff.len = 0; _dns_client_close_socket(server_info); pthread_mutex_unlock(&client.server_list_lock); return -1; } int _dns_client_send_udp(struct dns_server_info *server_info, void *packet, int len) { int send_len = 0; const struct sockaddr *addr = &server_info->addr; socklen_t addrlen = server_info->ai_addrlen; int ret = 0; if (server_info->fd <= 0) { return -1; } if (server_info->proxy) { if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { /*set packet len*/ _dns_client_copy_data_to_buffer(server_info, &len, sizeof(len)); return _dns_client_copy_data_to_buffer(server_info, packet, len); } send_len = proxy_conn_sendto(server_info->proxy, packet, len, 0, addr, addrlen); if (send_len != len) { _dns_client_close_socket(server_info); server_info->recv_buff.len = 0; if (server_info->send_buff.len > 0) { /* still remain request data, reconnect and send*/ ret = _dns_client_create_socket(server_info); } else { ret = 0; } if (ret != 0) { return -1; } _dns_client_copy_data_to_buffer(server_info, &len, sizeof(len)); return _dns_client_copy_data_to_buffer(server_info, packet, len); } return 0; } send_len = sendto(server_info->fd, packet, len, 0, NULL, 0); if (send_len != len) { goto errout; } return 0; errout: return -1; } void _dns_client_check_udp_nat(struct dns_query_struct *query) { struct dns_server_info *server_info = NULL; struct dns_server_group_member *group_member = NULL; /* For udp nat case. * when router reconnect to internet, udp port may always marked as UNREPLIED. * dns query will timeout, and cannot reconnect again, * create a new socket to communicate. */ pthread_mutex_lock(&client.server_list_lock); list_for_each_entry(group_member, &query->server_group->head, list) { server_info = group_member->server; if (server_info->type != DNS_SERVER_UDP) { continue; } if (server_info->last_send - 5 > server_info->last_recv) { server_info->recv_buff.len = 0; server_info->send_buff.len = 0; tlog(TLOG_DEBUG, "query server %s timeout.", server_info->ip); _dns_client_close_socket(server_info); } } pthread_mutex_unlock(&client.server_list_lock); } ================================================ FILE: src/dns_client/client_udp.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_UDP_H_ #define _DNS_CLIENT_UDP_H_ #include "dns_client.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_send_udp(struct dns_server_info *server_info, void *packet, int len); int _dns_client_create_socket_udp(struct dns_server_info *server_info); void _dns_client_check_udp_nat(struct dns_query_struct *query); int _dns_client_process_udp(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/conn_stream.c ================================================ /************************************************************************* * * 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 . */ #include "conn_stream.h" #include "smartdns/util.h" struct dns_conn_stream *_dns_client_conn_stream_new(void) { struct dns_conn_stream *stream = NULL; stream = zalloc(1, sizeof(*stream)); if (stream == NULL) { tlog(TLOG_ERROR, "malloc conn stream failed"); return NULL; } INIT_LIST_HEAD(&stream->server_list); INIT_LIST_HEAD(&stream->query_list); stream->quic_stream = NULL; stream->http2_stream = NULL; stream->server_info = NULL; stream->query = NULL; atomic_set(&stream->refcnt, 1); return stream; } void _dns_client_conn_stream_get(struct dns_conn_stream *stream) { if (atomic_inc_return(&stream->refcnt) <= 1) { BUG("stream ref is invalid"); } } void _dns_client_conn_stream_put(struct dns_conn_stream *stream) { int refcnt = atomic_dec_return(&stream->refcnt); if (refcnt) { if (refcnt < 0) { BUG("BUG: stream %p, refcnt is %d", stream, refcnt); } return; } if (stream->quic_stream) { SSL_free(stream->quic_stream); stream->quic_stream = NULL; } if (stream->http2_stream) { struct http2_stream *http2_stream = stream->http2_stream; stream->http2_stream = NULL; http2_stream_close(http2_stream); stream->server_info = NULL; } if (stream->query) { pthread_mutex_lock(&stream->query->lock); list_del_init(&stream->query_list); pthread_mutex_unlock(&stream->query->lock); stream->query = NULL; } if (stream->server_info) { pthread_mutex_lock(&stream->server_info->lock); list_del_init(&stream->server_list); pthread_mutex_unlock(&stream->server_info->lock); } free(stream); } void _dns_client_conn_server_streams_free(struct dns_server_info *server_info, struct dns_query_struct *query) { struct dns_conn_stream *stream = NULL; struct dns_conn_stream *tmp = NULL; pthread_mutex_lock(&server_info->lock); list_for_each_entry_safe(stream, tmp, &server_info->conn_stream_list, server_list) { if (stream->query != query) { continue; } list_del_init(&stream->server_list); stream->server_info = NULL; if (stream->quic_stream) { #if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC) SSL_stream_reset(stream->quic_stream, NULL, 0); #endif SSL_free(stream->quic_stream); stream->quic_stream = NULL; } if (stream->http2_stream) { http2_stream_close(stream->http2_stream); stream->http2_stream = NULL; } _dns_client_conn_stream_put(stream); } pthread_mutex_unlock(&server_info->lock); } ================================================ FILE: src/dns_client/conn_stream.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_CONN_STREAM_ #define _DNS_CLIENT_CONN_STREAM_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_client_conn_stream_put(struct dns_conn_stream *stream); void _dns_client_conn_stream_get(struct dns_conn_stream *stream); struct dns_conn_stream *_dns_client_conn_stream_new(void); void _dns_client_conn_server_streams_free(struct dns_server_info *server_info, struct dns_query_struct *query); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/dns_client.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/util.h" #include "client_http2.h" #include "client_http3.h" #include "client_https.h" #include "client_mdns.h" #include "client_quic.h" #include "client_socket.h" #include "client_tcp.h" #include "client_tls.h" #include "client_udp.h" #include "conn_stream.h" #include "dns_client.h" #include "ecs.h" #include "group.h" #include "packet.h" #include "pending_server.h" #include "proxy.h" #include "query.h" #include "server_info.h" #include "wake_event.h" static int is_client_init; struct dns_client client; void dns_client_flags_init(struct client_dns_server_flags *flags) { memset(flags, 0, sizeof(*flags)); } static int _dns_client_server_package_address_match(struct dns_server_info *server_info, struct sockaddr *addr, socklen_t addr_len) { if (server_info->type == DNS_SERVER_MDNS) { return 0; } if (addr_len != server_info->ai_addrlen) { return -1; } if (memcmp(addr, &server_info->addr, addr_len) != 0) { return -1; } return 0; } int _dns_client_recv(struct dns_server_info *server_info, unsigned char *inpacket, int inpacket_len, struct sockaddr *from, socklen_t from_len) { int len = 0; int i = 0; int j = 0; int qtype = 0; int qclass = 0; char domain[DNS_MAX_CNAME_LEN] = {0}; int rr_count = 0; struct dns_rrs *rrs = NULL; unsigned char packet_buff[DNS_PACKSIZE]; struct dns_packet *packet = (struct dns_packet *)packet_buff; int ret = 0; struct dns_query_struct *query = NULL; int request_num = 0; int has_opt = 0; packet->head.tc = 0; if (_dns_client_server_package_address_match(server_info, from, from_len) != 0) { tlog(TLOG_DEBUG, "packet from invalid server."); return -1; } stats_inc(&server_info->stats.recv_count); /* decode domain from udp packet */ len = dns_decode(packet, DNS_PACKSIZE, inpacket, inpacket_len); if (len != 0) { char host_name[DNS_MAX_CNAME_LEN]; tlog(TLOG_INFO, "decode failed, packet len = %d, tc = %d, id = %d, from = %s\n", inpacket_len, packet->head.tc, packet->head.id, get_host_by_addr(host_name, sizeof(host_name), from)); if (dns_conf.dns_save_fail_packet) { dns_packet_save(dns_conf.dns_save_fail_packet_dir, "client", host_name, inpacket, inpacket_len); } return -1; } /* not answer, return error */ if (packet->head.qr != DNS_OP_IQUERY) { tlog(TLOG_DEBUG, "message type error.\n"); return -1; } tlog(TLOG_DEBUG, "qdcount = %d, ancount = %d, nscount = %d, nrcount = %d, len = %d, id = %d, tc = %d, rd = %d, ra = %d, rcode " "= %d, payloadsize = %d\n", packet->head.qdcount, packet->head.ancount, packet->head.nscount, packet->head.nrcount, inpacket_len, packet->head.id, packet->head.tc, packet->head.rd, packet->head.ra, packet->head.rcode, dns_get_OPT_payload_size(packet)); /* get question */ for (j = 0; j < DNS_RRS_END && domain[0] == '\0'; j++) { rrs = dns_get_rrs_start(packet, (dns_rr_type)j, &rr_count); for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { dns_get_domain(rrs, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass); tlog(TLOG_DEBUG, "domain: %s qtype: %d qclass: %d\n", domain, qtype, qclass); break; } } if (dns_get_OPT_payload_size(packet) > 0) { has_opt = 1; } atomic_set(&server_info->is_alive, 1); int latency = get_tick_count() - server_info->send_tick; dns_stats_server_stats_avg_time_add(&server_info->stats, latency); /* get query reference */ query = _dns_client_get_request(domain, qtype, packet->head.id); if (query == NULL) { return 0; } if (has_opt == 0 && server_info->flags.result_flag & DNSSERVER_FLAG_CHECK_EDNS) { _dns_client_query_release(query); return 0; } /* avoid multiple replies */ if (_dns_replied_check_add(query, server_info) != 0) { _dns_client_query_release(query); return 0; } request_num = atomic_dec_return(&query->dns_request_sent); if (request_num < 0) { _dns_client_query_release(query); tlog(TLOG_ERROR, "send count is invalid, %d", request_num); return -1; } /* notify caller dns query result */ if (query->callback) { ret = query->callback(query->domain, DNS_QUERY_RESULT, server_info, packet, inpacket, inpacket_len, query->user_ptr); if (ret == DNS_CLIENT_ACTION_RETRY || ret == DNS_CLIENT_ACTION_DROP) { /* remove this result */ _dns_replied_check_remove(query, server_info); atomic_inc(&query->dns_request_sent); if (ret == DNS_CLIENT_ACTION_RETRY) { /* * retry immdiately * The socket needs to be re-created to avoid being limited, such as 1.1.1.1 */ pthread_mutex_lock(&client.server_list_lock); _dns_client_close_socket(server_info); pthread_mutex_unlock(&client.server_list_lock); _dns_client_retry_dns_query(query); } } else { if (ret == DNS_CLIENT_ACTION_OK) { query->has_result = 1; } else { tlog(TLOG_DEBUG, "query %s result is invalid, %d", query->domain, ret); } if (request_num == 0) { /* if all server replied, or done, stop query, release resource */ _dns_client_query_remove(query); } } } stats_inc(&server_info->stats.success_count); _dns_client_query_release(query); return 0; } static int _dns_client_process(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) { if (server_info->proxy) { int ret = _dns_proxy_handshake(server_info, event, now); if (ret != 0) { return ret; } } if (server_info->type == DNS_SERVER_UDP || server_info->type == DNS_SERVER_MDNS) { /* receive from udp */ return _dns_client_process_udp(server_info, event, now); } else if (server_info->type == DNS_SERVER_TCP) { /* receive from tcp */ return _dns_client_process_tcp(server_info, event, now); } else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS || server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) { /* receive from tls */ return _dns_client_process_tls(server_info, event, now); } else { return -1; } return 0; } static int _dns_client_send_http(struct dns_server_info *server_info, struct dns_query_struct *query, void *packet_data, int packet_data_len) { /* If ALPN is negotiated and is NOT h2, use HTTP/1.1 */ if (server_info->alpn_selected[0] != '\0' && strncmp(server_info->alpn_selected, "h2", 2) != 0) { return _dns_client_send_http1(server_info, packet_data, packet_data_len); } /* Default to HTTP/2 buffering (stream-based). If ALPN later turns out to be H1, _dns_client_process_https_streams will handle it. */ return _dns_client_send_http2(server_info, query, packet_data, packet_data_len); } static int _dns_client_check_server_prohibit(struct dns_server_info *server_info, int prohibit_time) { if (server_info->prohibit) { if (server_info->is_already_prohibit == 0) { server_info->is_already_prohibit = 1; _dns_server_inc_prohibit_server_num(server_info); time(&server_info->last_send); time(&server_info->last_recv); if (server_info->type != DNS_SERVER_MDNS) { tlog(TLOG_INFO, "server %s not alive, prohibit", server_info->ip); } _dns_client_shutdown_socket(server_info); } time_t now = 0; time(&now); if ((now - prohibit_time < server_info->last_send)) { return 1; } server_info->prohibit = 0; server_info->is_already_prohibit = 0; _dns_server_dec_prohibit_server_num(server_info); if (now - prohibit_time >= server_info->last_send) { _dns_client_close_socket(server_info); } } return 0; } static int _dns_client_send_one_packet(struct dns_server_info *server_info, struct dns_query_struct *query, void *packet_data, int packet_data_len) { int ret = 0; int send_err = 0; int retry = 0; atomic_inc(&query->dns_request_sent); stats_inc(&server_info->stats.total); errno = 0; server_info->send_tick = get_tick_count(); while (1) { switch (server_info->type) { case DNS_SERVER_UDP: /* udp query */ ret = _dns_client_send_udp(server_info, packet_data, packet_data_len); send_err = errno; break; case DNS_SERVER_TCP: /* tcp query */ ret = _dns_client_send_tcp(server_info, packet_data, packet_data_len); send_err = errno; break; case DNS_SERVER_TLS: /* tls query */ ret = _dns_client_send_tls(server_info, packet_data, packet_data_len); send_err = errno; break; case DNS_SERVER_HTTPS: /* https query - buffer raw data in stream, protocol determined later */ ret = _dns_client_send_http(server_info, query, packet_data, packet_data_len); send_err = errno; break; case DNS_SERVER_MDNS: /* mdns query */ ret = _dns_client_send_udp_mdns(server_info, packet_data, packet_data_len); send_err = errno; break; case DNS_SERVER_QUIC: /* quic query */ ret = _dns_client_send_quic(query, server_info, packet_data, packet_data_len); send_err = errno; break; case DNS_SERVER_HTTP3: /* http3 query */ ret = _dns_client_send_http3(query, server_info, packet_data, packet_data_len); send_err = errno; break; default: /* unsupported query type */ ret = -1; break; } if (ret != 0) { switch (send_err) { case EBADF: case ECONNRESET: case EPIPE: case EDESTADDRREQ: case EINVAL: case EISCONN: case ENOTCONN: case ENOTSOCK: case EOPNOTSUPP: { tlog(TLOG_DEBUG, "send query to %s failed, %s, type: %d", server_info->ip, strerror(send_err), server_info->type); _dns_client_close_socket(server_info); if (retry == 0) { retry = 1; if (_dns_client_create_socket(server_info) == 0) { continue; } } atomic_dec(&query->dns_request_sent); return -1; } default: break; } tlog(TLOG_DEBUG, "send query to %s failed, %s, type: %d", server_info->ip, strerror(send_err), server_info->type); time_t now = 0; time(&now); if (now - 10 > server_info->last_recv || send_err != ENOMEM) { server_info->prohibit = 1; } atomic_dec(&query->dns_request_sent); return -1; } break; } time(&server_info->last_send); return 0; } int _dns_client_send_packet(struct dns_query_struct *query, void *packet, int len) { struct dns_server_info *server_info = NULL; struct dns_server_group_member *group_member = NULL; struct dns_server_group_member *tmp = NULL; int ret = 0; int i = 0; int total_server = 0; int send_count = 0; void *packet_data = NULL; int packet_data_len = 0; unsigned char packet_data_buffer[DNS_IN_PACKSIZE]; int prohibit_time = 60; query->send_tick = get_tick_count(); /* send query to all dns servers */ atomic_inc(&query->dns_request_sent); for (i = 0; i < 2; i++) { total_server = 0; if (i == 1) { prohibit_time = 5; } /* fallback group exists, use fallback group */ if (atomic_read(&query->retry_count) == 1) { struct dns_server_group *fallback_server_group = _dns_client_get_group("fallback"); if (fallback_server_group != NULL) { query->server_group = fallback_server_group; } } pthread_mutex_lock(&client.server_list_lock); list_for_each_entry_safe(group_member, tmp, &query->server_group->head, list) { server_info = group_member->server; /* skip fallback server for first query */ if (server_info->flags.fallback && atomic_read(&query->retry_count) == DNS_QUERY_RETRY && i == 0) { continue; } if (_dns_client_check_server_prohibit(server_info, prohibit_time)) { continue; } total_server++; tlog(TLOG_DEBUG, "send query to server %s:%d, type:%d", server_info->ip, server_info->port, server_info->type); if (server_info->fd <= 0) { ret = _dns_client_create_socket(server_info); if (ret != 0) { server_info->prohibit = 1; continue; } } if (_dns_client_setup_server_packet(server_info, query, packet, len, packet_data_buffer, &packet_data, &packet_data_len) != 0) { continue; } if (_dns_client_send_one_packet(server_info, query, packet_data, packet_data_len) == 0) { send_count++; } } pthread_mutex_unlock(&client.server_list_lock); if (send_count > 0) { break; } } int num = atomic_dec_return(&query->dns_request_sent); if (num == 0 && send_count > 0) { _dns_client_query_remove(query); } if (send_count <= 0) { static time_t lastlog = 0; time_t now = 0; time(&now); if (now - lastlog > 120) { lastlog = now; tlog(TLOG_WARN, "send query %s to upstream server failed, total server number %d", query->domain, total_server); } return -1; } return 0; } int dns_client_query(const char *domain, int qtype, dns_client_callback callback, void *user_ptr, const char *group_name, struct dns_query_options *options) { struct dns_query_struct *query = NULL; int ret = 0; int unused __attribute__((unused)); if (domain == NULL) { goto errout; } if (atomic_read(&client.run) == 0) { goto errout; } query = zalloc(1, sizeof(*query)); if (query == NULL) { goto errout; } INIT_HLIST_NODE(&query->domain_node); INIT_LIST_HEAD(&query->dns_request_list); INIT_LIST_HEAD(&query->conn_stream_list); pthread_mutex_init(&query->lock, NULL); atomic_set(&query->refcnt, 0); atomic_set(&query->dns_request_sent, 0); atomic_set(&query->retry_count, DNS_QUERY_RETRY); hash_init(query->replied_map); safe_strncpy(query->domain, domain, DNS_MAX_CNAME_LEN); query->user_ptr = user_ptr; query->callback = callback; query->qtype = qtype; query->send_tick = 0; query->has_result = 0; query->server_group = _dns_client_get_dnsserver_group(group_name); if (query->server_group == NULL) { tlog(TLOG_ERROR, "get dns server group %s failed.", group_name); goto errout; } query->conf = dns_server_get_rule_group(options->conf_group_name); if (query->conf == NULL) { tlog(TLOG_ERROR, "get dns config group %s failed.", options->conf_group_name); goto errout; } if (_dns_client_query_parser_options(query, options) != 0) { tlog(TLOG_ERROR, "parser options for %s failed.", domain); goto errout; } _dns_client_query_get(query); /* add query to hashtable */ if (_dns_client_add_hashmap(query) != 0) { tlog(TLOG_ERROR, "add query to hash map failed."); goto errout; } /* send query */ _dns_client_query_get(query); ret = _dns_client_send_query(query); if (ret != 0) { _dns_client_query_release(query); goto errout_del_list; } pthread_mutex_lock(&client.domain_map_lock); if (hash_hashed(&query->domain_node)) { if (list_empty(&client.dns_request_list)) { _dns_client_do_wakeup_event(); } list_add_tail(&query->dns_request_list, &client.dns_request_list); } pthread_mutex_unlock(&client.domain_map_lock); tlog(TLOG_INFO, "request: %s, qtype: %d, id: %d, group: %s", domain, qtype, query->sid, query->server_group->group_name); _dns_client_query_release(query); return 0; errout_del_list: query->callback = NULL; _dns_client_query_remove(query); query = NULL; errout: if (query) { free(query); } return -1; } static void _dns_client_period_run_second(void) { _dns_client_check_tcp(); _dns_client_check_servers(); _dns_client_add_pending_servers(); } static void _dns_client_period_run(unsigned int msec) { struct dns_query_struct *query = NULL; struct dns_query_struct *tmp = NULL; LIST_HEAD(check_list); unsigned long now = get_tick_count(); /* get query which timed out to check list */ pthread_mutex_lock(&client.domain_map_lock); list_for_each_entry_safe(query, tmp, &client.dns_request_list, dns_request_list) { if ((now - DNS_QUERY_TIMEOUT >= query->send_tick) && query->send_tick > 0) { list_add(&query->period_list, &check_list); _dns_client_query_get(query); } } pthread_mutex_unlock(&client.domain_map_lock); list_for_each_entry_safe(query, tmp, &check_list, period_list) { /* free timed out query, and notify caller */ list_del_init(&query->period_list); /* check udp nat after retrying. */ if (atomic_read(&query->retry_count) == 1) { _dns_client_check_udp_nat(query); } _dns_client_retry_dns_query(query); _dns_client_query_release(query); } if (msec % 10 == 0) { _dns_client_period_run_second(); } } static void *_dns_client_work(void *arg) { struct epoll_event events[DNS_MAX_EVENTS + 1]; int num = 0; int i = 0; unsigned long now = {0}; unsigned int msec = 0; unsigned int sleep = 100; int sleep_time = 0; unsigned long expect_time = 0; unsigned long start_time = 0; now = get_tick_count(); start_time = now; expect_time = now + sleep; while (atomic_read(&client.run)) { now = get_tick_count(); if (now >= expect_time) { unsigned long elapsed_from_start = now - start_time; unsigned int current_period = (elapsed_from_start + sleep / 2) / sleep; if (current_period > msec) { msec = current_period; } expect_time = start_time + (msec + 1) * sleep; _dns_client_period_run(msec); msec++; /* When client is idle, the sleep time is 1000ms, to reduce CPU usage */ pthread_mutex_lock(&client.domain_map_lock); if (list_empty(&client.dns_request_list)) { if (msec % 10 != 0) { msec = ((msec / 10) + 1) * 10; expect_time = start_time + msec * sleep; } } pthread_mutex_unlock(&client.domain_map_lock); } sleep_time = (int)(expect_time - now); if (sleep_time < 0) { sleep_time = 0; } num = epoll_wait(client.epoll_fd, events, DNS_MAX_EVENTS, sleep_time); if (num < 0) { usleep(100000); continue; } for (i = 0; i < num; i++) { struct epoll_event *event = &events[i]; struct dns_server_info *server_info = (struct dns_server_info *)event->data.ptr; if (event->data.fd == client.fd_wakeup) { _dns_client_clear_wakeup_event(); continue; } if (server_info == NULL) { tlog(TLOG_WARN, "server info is invalid."); continue; } _dns_client_process(server_info, event, now); } } close(client.epoll_fd); client.epoll_fd = -1; return NULL; } int dns_client_init(void) { pthread_attr_t attr; int epollfd = -1; int fd_wakeup = -1; int ret = 0; if (is_client_init == 1) { return -1; } if (client.epoll_fd > 0) { return -1; } memset(&client, 0, sizeof(client)); pthread_attr_init(&attr); atomic_set(&client.dns_server_num, 0); atomic_set(&client.dns_server_prohibit_num, 0); atomic_set(&client.run_period, 0); epollfd = epoll_create1(EPOLL_CLOEXEC); if (epollfd < 0) { tlog(TLOG_ERROR, "create epoll failed, %s\n", strerror(errno)); goto errout; } pthread_mutex_init(&client.server_list_lock, NULL); INIT_LIST_HEAD(&client.dns_server_list); pthread_mutex_init(&client.domain_map_lock, NULL); hash_init(client.domain_map); hash_init(client.group); INIT_LIST_HEAD(&client.dns_request_list); if (dns_client_add_group(DNS_SERVER_GROUP_DEFAULT) != 0) { tlog(TLOG_ERROR, "add default server group failed."); goto errout; } if (_dns_client_add_mdns_server() != 0) { tlog(TLOG_ERROR, "add mdns server failed."); goto errout; } client.default_group = _dns_client_get_group(DNS_SERVER_GROUP_DEFAULT); client.epoll_fd = epollfd; atomic_set(&client.run, 1); /* start work task */ ret = pthread_create(&client.tid, &attr, _dns_client_work, NULL); if (ret != 0) { tlog(TLOG_ERROR, "create client work thread failed, %s\n", strerror(errno)); goto errout; } fd_wakeup = _dns_client_create_wakeup_event(); if (fd_wakeup < 0) { tlog(TLOG_ERROR, "create wakeup event failed, %s\n", strerror(errno)); goto errout; } client.fd_wakeup = fd_wakeup; is_client_init = 1; return 0; errout: if (client.tid) { void *retval = NULL; atomic_set(&client.run, 0); pthread_join(client.tid, &retval); client.tid = 0; } if (epollfd > 0) { close(epollfd); } if (fd_wakeup > 0) { close(fd_wakeup); } pthread_mutex_destroy(&client.server_list_lock); pthread_mutex_destroy(&client.domain_map_lock); return -1; } void dns_client_exit(void) { if (is_client_init == 0) { return; } if (client.tid) { void *ret = NULL; atomic_set(&client.run, 0); _dns_client_do_wakeup_event(); pthread_join(client.tid, &ret); client.tid = 0; } /* free all resources */ _dns_client_close_wakeup_event(); _dns_client_remove_all_pending_servers(); _dns_client_server_remove_all(); _dns_client_query_remove_all(); _dns_client_group_remove_all(); pthread_mutex_destroy(&client.server_list_lock); pthread_mutex_destroy(&client.domain_map_lock); if (client.ssl_ctx) { SSL_CTX_free(client.ssl_ctx); client.ssl_ctx = NULL; } if (client.ssl_quic_ctx) { SSL_CTX_free(client.ssl_quic_ctx); client.ssl_quic_ctx = NULL; } is_client_init = 0; } ================================================ FILE: src/dns_client/dns_client.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_H_ #define _DNS_CLIENT_H_ #include "smartdns/dns.h" #include "smartdns/dns_conf.h" #include "smartdns/dns_stats.h" #include "smartdns/http2.h" #include "smartdns/lib/atomic.h" #include "smartdns/lib/hashtable.h" #include "smartdns/lib/list.h" #include "smartdns/tlog.h" #include #include #include #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ #define DNS_MAX_HOSTNAME 256 #define DNS_MAX_EVENTS 256 #define DNS_HOSTNAME_LEN 128 #define DNS_TCP_BUFFER (32 * 1024) #define DNS_TCP_IDLE_TIMEOUT (60 * 10) #define DNS_TCP_CONNECT_TIMEOUT (5) #define DNS_QUERY_TIMEOUT (500) #define DNS_QUERY_RETRY (4) #define DNS_PENDING_SERVER_RETRY 60 #define SOCKET_PRIORITY (6) #define SOCKET_IP_TOS (IPTOS_LOWDELAY | IPTOS_RELIABILITY) /* ECS info */ struct dns_client_ecs { int enable; struct dns_opt_ecs ecs; }; /* TCP/TLS buffer */ struct dns_server_buff { unsigned char data[DNS_TCP_BUFFER]; int len; }; typedef enum dns_server_status { DNS_SERVER_STATUS_INIT = 0, DNS_SERVER_STATUS_CONNECTING, DNS_SERVER_STATUS_CONNECTIONLESS, DNS_SERVER_STATUS_CONNECTED, DNS_SERVER_STATUS_DISCONNECTED, } dns_server_status; /* dns server information */ struct dns_server_info { atomic_t refcnt; struct list_head list; struct list_head check_list; /* server ping handle */ struct ping_host_struct *ping_host; char host[DNS_HOSTNAME_LEN]; char ip[DNS_MAX_HOSTNAME]; int port; char proxy_name[DNS_HOSTNAME_LEN]; /* server type */ dns_server_type_t type; long long so_mark; int drop_packet_latency_ms; /* client socket */ int fd; int ttl; int ttl_range; SSL *ssl; int ssl_write_len; int ssl_want_write; SSL_CTX *ssl_ctx; SSL_SESSION *ssl_session; BIO_METHOD *bio_method; struct proxy_conn *proxy; pthread_mutex_t lock; char skip_check_cert; dns_server_status status; struct dns_server_buff send_buff; struct dns_server_buff recv_buff; time_t last_send; time_t last_recv; unsigned long send_tick; int prohibit; atomic_t is_alive; int is_already_prohibit; /* server addr info */ unsigned short ai_family; socklen_t ai_addrlen; union { struct sockaddr_in in; struct sockaddr_in6 in6; struct sockaddr addr; }; struct client_dns_server_flags flags; /* ECS */ struct dns_client_ecs ecs_ipv4; struct dns_client_ecs ecs_ipv6; struct dns_server_stats stats; struct list_head conn_stream_list; /* HTTP/2 context - connection level, shared across requests */ struct http2_ctx *http2_ctx; char alpn_selected[32]; dns_server_security_status security_status; }; struct dns_server_pending_group { struct list_head list; char group_name[DNS_GROUP_NAME_LEN]; }; struct dns_server_pending { struct list_head list; struct list_head retry_list; atomic_t refcnt; char host[DNS_HOSTNAME_LEN]; char ipv4[DNS_HOSTNAME_LEN]; char ipv6[DNS_HOSTNAME_LEN]; unsigned int ping_time_v6; unsigned int ping_time_v4; unsigned int has_v4; unsigned int has_v6; unsigned int query_v4; unsigned int query_v6; unsigned int has_soa_v4; unsigned int has_soa_v6; /* server type */ dns_server_type_t type; int retry_cnt; int port; struct client_dns_server_flags flags; struct list_head group_list; }; /* upstream server group member */ struct dns_server_group_member { struct list_head list; struct dns_server_info *server; }; /* upstream server groups */ struct dns_server_group { char group_name[DNS_GROUP_NAME_LEN]; struct hlist_node node; struct list_head head; }; /* dns client */ struct dns_client { pthread_t tid; atomic_t run; int epoll_fd; /* dns server list */ pthread_mutex_t server_list_lock; struct list_head dns_server_list; struct dns_server_group *default_group; SSL_CTX *ssl_ctx; SSL_CTX *ssl_quic_ctx; int ssl_verify_skip; /* query list */ struct list_head dns_request_list; atomic_t run_period; atomic_t dns_server_num; atomic_t dns_server_prohibit_num; /* query domain hash table, key: sid + domain */ pthread_mutex_t domain_map_lock; DECLARE_HASHTABLE(domain_map, 6); DECLARE_HASHTABLE(group, 4); int fd_wakeup; }; /* dns replied server info */ struct dns_query_replied { struct hlist_node node; struct dns_server_info *server; }; struct dns_conn_stream { atomic_t refcnt; struct list_head query_list; struct list_head server_list; struct dns_server_buff send_buff; struct dns_server_buff recv_buff; struct dns_query_struct *query; struct dns_server_info *server_info; SSL *quic_stream; struct http2_stream *http2_stream; dns_server_type_t type; }; /* query struct */ struct dns_query_struct { struct list_head dns_request_list; atomic_t refcnt; struct dns_server_group *server_group; struct dns_conf_group *conf; struct list_head conn_stream_list; /* query id, hash key sid + domain*/ char domain[DNS_MAX_CNAME_LEN]; unsigned short sid; struct hlist_node domain_node; struct list_head period_list; /* dns query type */ int qtype; /* dns query number */ atomic_t dns_request_sent; unsigned long send_tick; /* caller notification */ dns_client_callback callback; void *user_ptr; /* retry count */ atomic_t retry_count; /* has result */ int has_result; /* ECS */ struct dns_client_ecs ecs; /* EDNS0_DO */ int edns0_do; /* replied hash table */ DECLARE_HASHTABLE(replied_map, 4); pthread_mutex_t lock; }; extern struct dns_client client; int _dns_client_recv(struct dns_server_info *server_info, unsigned char *inpacket, int inpacket_len, struct sockaddr *from, socklen_t from_len); int _dns_client_send_packet(struct dns_query_struct *query, void *packet, int len); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/ecs.c ================================================ /************************************************************************* * * 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 . */ #include "ecs.h" #include "smartdns/util.h" static int _dns_client_setup_ecs(char *ip, int subnet, struct dns_client_ecs *ecs) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); if (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) { return -1; } switch (addr.ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&addr; memcpy(&ecs->ecs.addr, &addr_in->sin_addr.s_addr, 4); ecs->ecs.source_prefix = subnet; ecs->ecs.scope_prefix = 0; ecs->ecs.family = DNS_OPT_ECS_FAMILY_IPV4; ecs->enable = 1; } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { ecs->ecs.source_prefix = subnet; ecs->ecs.scope_prefix = 0; ecs->ecs.family = DNS_OPT_ECS_FAMILY_IPV4; ecs->enable = 1; } else { memcpy(&ecs->ecs.addr, addr_in6->sin6_addr.s6_addr, 16); ecs->ecs.source_prefix = subnet; ecs->ecs.scope_prefix = 0; ecs->ecs.family = DNS_ADDR_FAMILY_IPV6; ecs->enable = 1; } } break; default: return -1; } return 0; } int _dns_client_server_add_ecs(struct dns_server_info *server_info, struct client_dns_server_flags *flags) { int ret = 0; if (flags == NULL) { return 0; } if (flags->ipv4_ecs.enable) { ret = _dns_client_setup_ecs(flags->ipv4_ecs.ip, flags->ipv4_ecs.subnet, &server_info->ecs_ipv4); } if (flags->ipv6_ecs.enable) { ret |= _dns_client_setup_ecs(flags->ipv6_ecs.ip, flags->ipv6_ecs.subnet, &server_info->ecs_ipv6); } return ret; } int _dns_client_dns_add_ecs(struct dns_query_struct *query, struct dns_packet *packet) { if (query->ecs.enable == 0) { return 0; } return dns_add_OPT_ECS(packet, &query->ecs.ecs); } int _dns_client_query_setup_default_ecs(struct dns_query_struct *query) { struct dns_conf_group *conf = query->conf; struct dns_edns_client_subnet *ecs_conf = NULL; if (query->qtype == DNS_T_A && conf->ipv4_ecs.enable) { ecs_conf = &conf->ipv4_ecs; } else if (query->qtype == DNS_T_AAAA && conf->ipv6_ecs.enable) { ecs_conf = &conf->ipv6_ecs; } else { if (conf->ipv4_ecs.enable) { ecs_conf = &conf->ipv4_ecs; } else if (conf->ipv6_ecs.enable) { ecs_conf = &conf->ipv6_ecs; } } if (ecs_conf == NULL) { return 0; } struct dns_client_ecs ecs; if (_dns_client_setup_ecs(ecs_conf->ip, ecs_conf->subnet, &ecs) != 0) { return -1; } memcpy(&query->ecs, &ecs, sizeof(query->ecs)); return 0; } ================================================ FILE: src/dns_client/ecs.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_ECS_ #define _DNS_CLIENT_ECS_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_query_setup_default_ecs(struct dns_query_struct *query); int _dns_client_dns_add_ecs(struct dns_query_struct *query, struct dns_packet *packet); int _dns_client_server_add_ecs(struct dns_server_info *server_info, struct client_dns_server_flags *flags); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/group.c ================================================ /************************************************************************* * * 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 . */ #include "group.h" #include "pending_server.h" #include "server_info.h" #include "smartdns/util.h" /* get server group by name */ struct dns_server_group *_dns_client_get_group(const char *group_name) { uint32_t key = 0; struct dns_server_group *group = NULL; struct hlist_node *tmp = NULL; if (group_name == NULL) { return NULL; } key = hash_string(group_name); hash_for_each_possible_safe(client.group, group, tmp, node, key) { if (strncmp(group->group_name, group_name, DNS_GROUP_NAME_LEN) != 0) { continue; } return group; } return NULL; } /* get server group by name */ struct dns_server_group *_dns_client_get_dnsserver_group(const char *group_name) { struct dns_server_group *group = _dns_client_get_group(group_name); if (group == NULL) { goto use_default; } else { if (list_empty(&group->head)) { tlog(TLOG_DEBUG, "server group %s not exist, use default server group.", group_name); goto use_default; } } return group; use_default: return client.default_group; } /* add server to group */ int _dns_client_add_to_group(const char *group_name, struct dns_server_info *server_info) { struct dns_server_group *group = NULL; struct dns_server_group_member *group_member = NULL; group = _dns_client_get_group(group_name); if (group == NULL) { tlog(TLOG_ERROR, "group %s not exist.", group_name); return -1; } group_member = zalloc(1, sizeof(*group_member)); if (group_member == NULL) { tlog(TLOG_ERROR, "malloc memory failed."); goto errout; } group_member->server = server_info; dns_client_server_info_get(server_info); list_add(&group_member->list, &group->head); return 0; errout: if (group_member) { free(group_member); } return -1; } int dns_client_add_to_group(const char *group_name, const char *server_ip, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags) { return _dns_client_add_to_group_pending(group_name, server_ip, port, server_type, flags, 1); } /* free group member */ static int _dns_client_remove_member(struct dns_server_group_member *group_member) { if (group_member == NULL) { return -1; } if (group_member->server) { dns_client_server_info_release(group_member->server); } list_del_init(&group_member->list); free(group_member); return 0; } static int _dns_client_remove_from_group(struct dns_server_group *group, struct dns_server_info *server_info) { struct dns_server_group_member *group_member = NULL; struct dns_server_group_member *tmp = NULL; list_for_each_entry_safe(group_member, tmp, &group->head, list) { if (group_member->server != server_info) { continue; } _dns_client_remove_member(group_member); } return 0; } int _dns_client_remove_server_from_groups(struct dns_server_info *server_info) { struct dns_server_group *group = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(client.group, i, tmp, group, node) { _dns_client_remove_from_group(group, server_info); } return 0; } int dns_client_remove_from_group(const char *group_name, const char *server_ip, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags) { struct dns_server_info *server_info = NULL; struct dns_server_group *group = NULL; server_info = _dns_client_get_server(server_ip, port, server_type, flags); if (server_info == NULL) { return -1; } group = _dns_client_get_group(group_name); if (group == NULL) { return -1; } return _dns_client_remove_from_group(group, server_info); } int dns_client_add_group(const char *group_name) { uint32_t key = 0; struct dns_server_group *group = NULL; if (group_name == NULL) { return -1; } if (_dns_client_get_group(group_name) != NULL) { return 0; } group = zalloc(1, sizeof(*group)); if (group == NULL) { goto errout; } INIT_LIST_HEAD(&group->head); safe_strncpy(group->group_name, group_name, DNS_GROUP_NAME_LEN); key = hash_string(group_name); hash_add(client.group, &group->node, key); return 0; errout: if (group) { free(group); group = NULL; } return -1; } static int _dns_client_remove_group(struct dns_server_group *group) { struct dns_server_group_member *group_member = NULL; struct dns_server_group_member *tmp = NULL; if (group == NULL) { return 0; } list_for_each_entry_safe(group_member, tmp, &group->head, list) { _dns_client_remove_member(group_member); } hash_del(&group->node); free(group); return 0; } int dns_client_remove_group(const char *group_name) { uint32_t key = 0; struct dns_server_group *group = NULL; struct hlist_node *tmp = NULL; if (group_name == NULL) { return -1; } key = hash_string(group_name); hash_for_each_possible_safe(client.group, group, tmp, node, key) { if (strncmp(group->group_name, group_name, DNS_GROUP_NAME_LEN) != 0) { continue; } _dns_client_remove_group(group); return 0; } return 0; } void _dns_client_group_remove_all(void) { struct dns_server_group *group = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(client.group, i, tmp, group, node) { _dns_client_remove_group(group); } } ================================================ FILE: src/dns_client/group.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_GROUP_ #define _DNS_CLIENT_GROUP_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_remove_server_from_groups(struct dns_server_info *server_info); void _dns_client_group_remove_all(void); struct dns_server_group *_dns_client_get_dnsserver_group(const char *group_name); int _dns_client_add_to_group(const char *group_name, struct dns_server_info *server_info); struct dns_server_group *_dns_client_get_group(const char *group_name); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/packet.c ================================================ /************************************************************************* * * 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 . */ #include "packet.h" #include "smartdns/util.h" int _dns_client_setup_server_packet(struct dns_server_info *server_info, struct dns_query_struct *query, void *default_packet, int default_packet_len, unsigned char *packet_data_buffer, void **packet_data, int *packet_data_len) { unsigned char packet_buff[DNS_PACKSIZE]; struct dns_packet *packet = (struct dns_packet *)packet_buff; struct dns_head head; int encode_len = 0; int repack = 0; int hitchhiking = 0; *packet_data = default_packet; *packet_data_len = default_packet_len; if (server_info->ecs_ipv4.enable == true || server_info->ecs_ipv6.enable == true) { repack = 1; } if ((server_info->flags.server_flag & SERVER_FLAG_HITCHHIKING) != 0) { hitchhiking = 1; repack = 1; } if (repack == 0) { /* no need to encode packet */ return 0; } /* init dns packet head */ memset(&head, 0, sizeof(head)); head.id = query->sid; head.qr = DNS_QR_QUERY; head.opcode = DNS_OP_QUERY; head.aa = 0; head.rd = 1; head.ra = 0; head.ad = query->edns0_do; head.rcode = 0; if (dns_packet_init(packet, DNS_PACKSIZE, &head) != 0) { tlog(TLOG_ERROR, "init packet failed."); return -1; } if (hitchhiking != 0 && dns_add_domain(packet, "-", query->qtype, DNS_C_IN) != 0) { tlog(TLOG_ERROR, "add domain to packet failed."); return -1; } /* add question */ if (dns_add_domain(packet, query->domain, query->qtype, DNS_C_IN) != 0) { tlog(TLOG_ERROR, "add domain to packet failed."); return -1; } dns_set_OPT_payload_size(packet, DNS_IN_PACKSIZE); if (query->edns0_do) { dns_set_OPT_option(packet, DNS_OPT_FLAG_DO); } if (server_info->flags.tcp_keepalive > 0) { dns_add_OPT_TCP_KEEPALIVE(packet, server_info->flags.tcp_keepalive); } if ((query->qtype == DNS_T_A && server_info->ecs_ipv4.enable)) { dns_add_OPT_ECS(packet, &server_info->ecs_ipv4.ecs); } else if ((query->qtype == DNS_T_AAAA && server_info->ecs_ipv6.enable)) { dns_add_OPT_ECS(packet, &server_info->ecs_ipv6.ecs); } else if (query->qtype == DNS_T_AAAA || query->qtype == DNS_T_A || server_info->flags.subnet_all_query_types) { if (server_info->ecs_ipv6.enable) { dns_add_OPT_ECS(packet, &server_info->ecs_ipv6.ecs); } else if (server_info->ecs_ipv4.enable) { dns_add_OPT_ECS(packet, &server_info->ecs_ipv4.ecs); } } /* encode packet */ encode_len = dns_encode(packet_data_buffer, DNS_IN_PACKSIZE, packet); if (encode_len <= 0) { tlog(TLOG_ERROR, "encode query failed."); return -1; } if (encode_len > DNS_IN_PACKSIZE) { BUG("size is invalid."); return -1; } *packet_data = packet_data_buffer; *packet_data_len = encode_len; return 0; } ================================================ FILE: src/dns_client/packet.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_PACKET_ #define _DNS_CLIENT_PACKET_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_client_setup_server_packet(struct dns_server_info *server_info, struct dns_query_struct *query, void *default_packet, int default_packet_len, unsigned char *packet_data_buffer, void **packet_data, int *packet_data_len); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/pending_server.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "pending_server.h" #include "group.h" #include "server_info.h" #include "wake_event.h" #include "smartdns/dns_server.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include static LIST_HEAD(pending_servers); static pthread_mutex_t pending_server_mutex = PTHREAD_MUTEX_INITIALIZER; static int dns_client_has_bootstrap_dns = 0; /* get addr info */ struct addrinfo *_dns_client_getaddr(const char *host, char *port, int type, int protocol) { struct addrinfo hints; struct addrinfo *result = NULL; int ret = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = type; hints.ai_protocol = protocol; ret = getaddrinfo(host, port, &hints, &result); if (ret != 0) { tlog(TLOG_WARN, "get addr info failed. %s\n", gai_strerror(ret)); tlog(TLOG_WARN, "host = %s, port = %s, type = %d, protocol = %d", host, port, type, protocol); goto errout; } return result; errout: if (result) { freeaddrinfo(result); } return NULL; } static int _dns_client_resolv_ip_by_host(const char *host, char *ip, int ip_len) { struct addrinfo *gai = NULL; gai = _dns_client_getaddr(host, NULL, SOCK_STREAM, 0); if (gai == NULL) { return -1; } if (get_host_by_addr(ip, ip_len, gai->ai_addr) == NULL) { freeaddrinfo(gai); return -1; } freeaddrinfo(gai); return 0; } int _dns_client_add_to_pending_group(const char *group_name, const char *server_ip, int port, dns_server_type_t server_type, const struct client_dns_server_flags *flags) { struct dns_server_pending *item = NULL; struct dns_server_pending *tmp = NULL; struct dns_server_pending *pending = NULL; struct dns_server_pending_group *group = NULL; if (group_name == NULL || server_ip == NULL) { goto errout; } pthread_mutex_lock(&pending_server_mutex); list_for_each_entry_safe(item, tmp, &pending_servers, list) { if (memcmp(&item->flags, flags, sizeof(*flags)) != 0) { continue; } if (strncmp(item->host, server_ip, DNS_HOSTNAME_LEN) == 0 && item->port == port && item->type == server_type) { pending = item; break; } } pthread_mutex_unlock(&pending_server_mutex); if (pending == NULL) { tlog(TLOG_ERROR, "cannot found server for group %s: %s, %d, %d", group_name, server_ip, port, server_type); goto errout; } group = zalloc(1, sizeof(*group)); if (group == NULL) { goto errout; } safe_strncpy(group->group_name, group_name, DNS_GROUP_NAME_LEN); pthread_mutex_lock(&pending_server_mutex); list_add_tail(&group->list, &pending->group_list); pthread_mutex_unlock(&pending_server_mutex); return 0; errout: if (group) { free(group); } return -1; } static void _dns_client_server_pending_get(struct dns_server_pending *pending) { if (atomic_inc_return(&pending->refcnt) <= 0) { BUG("pending ref is invalid"); } } static void _dns_client_server_pending_release(struct dns_server_pending *pending) { struct dns_server_pending_group *group = NULL; struct dns_server_pending_group *tmp = NULL; int refcnt = atomic_dec_return(&pending->refcnt); if (refcnt) { if (refcnt < 0) { BUG("BUG: pending refcnt is %d", refcnt); } return; } pthread_mutex_lock(&pending_server_mutex); list_for_each_entry_safe(group, tmp, &pending->group_list, list) { list_del_init(&group->list); free(group); } list_del_init(&pending->list); pthread_mutex_unlock(&pending_server_mutex); free(pending); } static void _dns_client_server_pending_remove(struct dns_server_pending *pending) { pthread_mutex_lock(&pending_server_mutex); list_del_init(&pending->list); pthread_mutex_unlock(&pending_server_mutex); _dns_client_server_pending_release(pending); } static int _dns_client_server_pending(const char *server_ip, int port, dns_server_type_t server_type, const struct client_dns_server_flags *flags) { struct dns_server_pending *pending = NULL; pending = zalloc(1, sizeof(*pending)); if (pending == NULL) { tlog(TLOG_ERROR, "malloc failed"); goto errout; } safe_strncpy(pending->host, server_ip, DNS_HOSTNAME_LEN); pending->port = port; pending->type = server_type; pending->ping_time_v4 = -1; pending->ping_time_v6 = -1; pending->ipv4[0] = 0; pending->ipv6[0] = 0; pending->has_v4 = 0; pending->has_v6 = 0; _dns_client_server_pending_get(pending); INIT_LIST_HEAD(&pending->group_list); INIT_LIST_HEAD(&pending->retry_list); memcpy(&pending->flags, flags, sizeof(struct client_dns_server_flags)); pthread_mutex_lock(&pending_server_mutex); list_add_tail(&pending->list, &pending_servers); atomic_set(&client.run_period, 1); pthread_mutex_unlock(&pending_server_mutex); _dns_client_do_wakeup_event(); return 0; errout: if (pending) { free(pending); } return -1; } int _dns_client_add_server_pending(const char *server_ip, const char *server_host, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags, int is_pending) { int ret = 0; char server_ip_tmp[DNS_HOSTNAME_LEN] = {0}; if (server_type >= DNS_SERVER_TYPE_END) { tlog(TLOG_ERROR, "server type is invalid."); return -1; } if (check_is_ipaddr(server_ip) && is_pending) { ret = _dns_client_server_pending(server_ip, port, server_type, flags); if (ret == 0) { tlog(TLOG_INFO, "add pending server %s", server_ip); return 0; } } else if (check_is_ipaddr(server_ip) && is_pending == 0) { if (_dns_client_resolv_ip_by_host(server_ip, server_ip_tmp, sizeof(server_ip_tmp)) != 0) { tlog(TLOG_ERROR, "resolve %s failed.", server_ip); return -1; } tlog(TLOG_INFO, "resolve %s to %s.", server_ip, server_ip_tmp); server_ip = server_ip_tmp; } /* add server */ ret = _dns_client_server_add(server_ip, server_host, port, server_type, flags); if (ret != 0) { goto errout; } if ((flags->server_flag & SERVER_FLAG_EXCLUDE_DEFAULT) == 0 || dns_conf_exist_bootstrap_dns) { dns_client_has_bootstrap_dns = 1; } return 0; errout: return -1; } static int _dns_client_pending_server_resolve(const struct dns_result *result, void *user_ptr) { struct dns_server_pending *pending = user_ptr; int ret = 0; int has_soa = 0; if (result->rtcode == DNS_RC_NXDOMAIN || result->has_soa == 1 || result->rtcode == DNS_RC_REFUSED || (result->rtcode == DNS_RC_NOERROR && result->ip_num == 0 && result->ip == NULL)) { has_soa = 1; } if (result->addr_type == DNS_T_A) { pending->ping_time_v4 = -1; if (result->rtcode == DNS_RC_NOERROR && result->ip_num > 0) { pending->has_v4 = 1; pending->ping_time_v4 = result->ping_time; pending->has_soa_v4 = 0; safe_strncpy(pending->ipv4, result->ip, DNS_HOSTNAME_LEN); } else if (has_soa) { pending->has_v4 = 0; pending->ping_time_v4 = -1; pending->has_soa_v4 = 1; } } else if (result->addr_type == DNS_T_AAAA) { pending->ping_time_v6 = -1; if (result->rtcode == DNS_RC_NOERROR && result->ip_num > 0) { pending->has_v6 = 1; pending->ping_time_v6 = result->ping_time; pending->has_soa_v6 = 0; safe_strncpy(pending->ipv6, result->ip, DNS_HOSTNAME_LEN); } else if (has_soa) { pending->has_v6 = 0; pending->ping_time_v6 = -1; pending->has_soa_v6 = 1; } } else { ret = -1; } _dns_client_server_pending_release(pending); return ret; } /* add server to group */ int _dns_client_add_to_group_pending(const char *group_name, const char *server_ip, int port, dns_server_type_t server_type, const struct client_dns_server_flags *flags, int is_pending) { struct dns_server_info *server_info = NULL; if (group_name == NULL || server_ip == NULL) { return -1; } server_info = _dns_client_get_server(server_ip, port, server_type, flags); if (server_info == NULL) { if (is_pending == 0) { tlog(TLOG_ERROR, "add server %s:%d to group %s failed", server_ip, port, group_name); return -1; } return _dns_client_add_to_pending_group(group_name, server_ip, port, server_type, flags); } return _dns_client_add_to_group(group_name, server_info); } static int _dns_client_add_pendings(struct dns_server_pending *pending, char *ip) { struct dns_server_pending_group *group = NULL; struct dns_server_pending_group *tmp = NULL; char ip_tmp[DNS_HOSTNAME_LEN] = {0}; if (check_is_ipaddr(ip) != 0) { if (_dns_client_resolv_ip_by_host(ip, ip_tmp, sizeof(ip_tmp)) != 0) { tlog(TLOG_WARN, "resolv %s failed.", ip); return -1; } tlog(TLOG_INFO, "resolv %s to %s.", ip, ip_tmp); ip = ip_tmp; } if (_dns_client_add_server_pending(ip, pending->host, pending->port, pending->type, &pending->flags, 0) != 0) { return -1; } list_for_each_entry_safe(group, tmp, &pending->group_list, list) { if (_dns_client_add_to_group_pending(group->group_name, ip, pending->port, pending->type, &pending->flags, 0) != 0) { tlog(TLOG_WARN, "add server to group failed, skip add."); } list_del_init(&group->list); free(group); } return 0; } void _dns_client_remove_all_pending_servers(void) { struct dns_server_pending *pending = NULL; struct dns_server_pending *tmp = NULL; LIST_HEAD(remove_list); pthread_mutex_lock(&pending_server_mutex); list_for_each_entry_safe(pending, tmp, &pending_servers, list) { list_del_init(&pending->list); list_add(&pending->retry_list, &remove_list); _dns_client_server_pending_get(pending); } pthread_mutex_unlock(&pending_server_mutex); list_for_each_entry_safe(pending, tmp, &remove_list, retry_list) { list_del_init(&pending->retry_list); _dns_client_server_pending_remove(pending); _dns_client_server_pending_release(pending); } } void _dns_client_add_pending_servers(void) { #ifdef TEST const int delay_value = 1; #else const int delay_value = 3; #endif struct dns_server_pending *pending = NULL; struct dns_server_pending *tmp = NULL; static int delay = delay_value; LIST_HEAD(retry_list); /* add pending server after 3 seconds */ if (++delay < delay_value) { return; } delay = 0; pthread_mutex_lock(&pending_server_mutex); if (list_empty(&pending_servers)) { atomic_set(&client.run_period, 0); } else { atomic_set(&client.run_period, 1); } list_for_each_entry_safe(pending, tmp, &pending_servers, list) { list_add(&pending->retry_list, &retry_list); _dns_client_server_pending_get(pending); } pthread_mutex_unlock(&pending_server_mutex); list_for_each_entry_safe(pending, tmp, &retry_list, retry_list) { /* send dns type A, AAAA query to bootstrap DNS server */ int add_success = 0; char *dnsserver_ip = NULL; /* if has no bootstrap DNS, just call getaddrinfo to get address */ if (dns_client_has_bootstrap_dns == 0) { list_del_init(&pending->retry_list); _dns_client_server_pending_release(pending); pending->retry_cnt++; if (_dns_client_add_pendings(pending, pending->host) != 0) { pthread_mutex_unlock(&pending_server_mutex); tlog(TLOG_INFO, "add pending DNS server %s from resolv.conf failed, retry %d...", pending->host, pending->retry_cnt - 1); if (pending->retry_cnt - 1 > DNS_PENDING_SERVER_RETRY) { tlog(TLOG_WARN, "add pending DNS server %s from resolv.conf failed, exit...", pending->host); exit(1); } continue; } _dns_client_server_pending_release(pending); continue; } if (pending->query_v4 == 0) { pending->query_v4 = 1; _dns_client_server_pending_get(pending); if (dns_server_query(pending->host, DNS_T_A, NULL, _dns_client_pending_server_resolve, pending) != 0) { _dns_client_server_pending_release(pending); pending->query_v4 = 0; } } if (pending->query_v6 == 0) { pending->query_v6 = 1; _dns_client_server_pending_get(pending); if (dns_server_query(pending->host, DNS_T_AAAA, NULL, _dns_client_pending_server_resolve, pending) != 0) { _dns_client_server_pending_release(pending); pending->query_v6 = 0; } } list_del_init(&pending->retry_list); _dns_client_server_pending_release(pending); /* if both A, AAAA has query result, select fastest IP address */ if (pending->has_v4 && pending->has_v6) { if (pending->ping_time_v4 <= pending->ping_time_v6 && pending->ipv4[0]) { dnsserver_ip = pending->ipv4; } else { dnsserver_ip = pending->ipv6; } } else if (pending->has_v4) { dnsserver_ip = pending->ipv4; } else if (pending->has_v6) { dnsserver_ip = pending->ipv6; } if (dnsserver_ip && dnsserver_ip[0]) { if (_dns_client_add_pendings(pending, dnsserver_ip) == 0) { add_success = 1; } } pending->retry_cnt++; if (pending->retry_cnt == 1) { continue; } if (dnsserver_ip == NULL && pending->has_soa_v4 && pending->has_soa_v6) { tlog(TLOG_WARN, "add pending DNS server %s failed, no such host.", pending->host); _dns_client_server_pending_remove(pending); continue; } if (pending->retry_cnt - 1 > DNS_PENDING_SERVER_RETRY || add_success) { if (add_success == 0) { tlog(TLOG_WARN, "add pending DNS server %s failed.", pending->host); } _dns_client_server_pending_remove(pending); } else { tlog(TLOG_INFO, "add pending DNS server %s failed, retry %d...", pending->host, pending->retry_cnt - 1); pending->query_v4 = 0; pending->query_v6 = 0; } } } ================================================ FILE: src/dns_client/pending_server.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_PENDING_SERVER_ #define _DNS_CLIENT_PENDING_SERVER_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ struct addrinfo *_dns_client_getaddr(const char *host, char *port, int type, int protocol); int _dns_client_add_server_pending(const char *server_ip, const char *server_host, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags, int is_pending); int _dns_client_add_to_pending_group(const char *group_name, const char *server_ip, int port, dns_server_type_t server_type, const struct client_dns_server_flags *flags); int _dns_client_add_to_group_pending(const char *group_name, const char *server_ip, int port, dns_server_type_t server_type, const struct client_dns_server_flags *flags, int is_pending); void _dns_client_remove_all_pending_servers(void); void _dns_client_add_pending_servers(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/proxy.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/proxy.h" #include "smartdns/util.h" #include "proxy.h" #include "client_socket.h" #include #include int _dns_proxy_handshake(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) { struct epoll_event fd_event; proxy_handshake_state ret; int fd; int retval = -1; int epoll_op = EPOLL_CTL_MOD; pthread_mutex_lock(&server_info->lock); if (server_info->proxy == NULL) { pthread_mutex_unlock(&server_info->lock); return -1; } ret = proxy_conn_handshake(server_info->proxy); fd = server_info->fd; if (ret == PROXY_HANDSHAKE_OK) { pthread_mutex_unlock(&server_info->lock); return 0; } if (ret == PROXY_HANDSHAKE_ERR || fd < 0) { goto errout; } memset(&fd_event, 0, sizeof(fd_event)); if (ret == PROXY_HANDSHAKE_CONNECTED) { fd_event.events = EPOLLIN; if (server_info->type == DNS_SERVER_UDP || server_info->type == DNS_SERVER_HTTP3 || server_info->type == DNS_SERVER_QUIC) { epoll_ctl(client.epoll_fd, EPOLL_CTL_DEL, fd, NULL); event->events = 0; fd = proxy_conn_get_udpfd(server_info->proxy); if (fd < 0) { tlog(TLOG_ERROR, "get udp fd failed"); goto errout; } set_fd_nonblock(fd, 1); if (server_info->so_mark >= 0) { unsigned int so_mark = server_info->so_mark; if (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) { tlog(TLOG_DEBUG, "set socket mark failed, %s", strerror(errno)); } } server_info->fd = fd; epoll_op = EPOLL_CTL_ADD; if (server_info->type == DNS_SERVER_UDP) { server_info->status = DNS_SERVER_STATUS_CONNECTED; } else { /* do handshake for quic */ server_info->status = DNS_SERVER_STATUS_CONNECTING; fd_event.events |= EPOLLOUT; } } else { fd_event.events |= EPOLLOUT; } retval = 0; } if (ret == PROXY_HANDSHAKE_WANT_READ) { fd_event.events = EPOLLIN; } else if (ret == PROXY_HANDSHAKE_WANT_WRITE) { fd_event.events = EPOLLOUT | EPOLLIN; } fd_event.data.ptr = server_info; if (epoll_ctl(client.epoll_fd, epoll_op, fd, &fd_event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); goto errout; } pthread_mutex_unlock(&server_info->lock); return retval; errout: server_info->recv_buff.len = 0; server_info->send_buff.len = 0; if (server_info->proxy) { _dns_client_close_socket(server_info); } pthread_mutex_unlock(&server_info->lock); return -1; } ================================================ FILE: src/dns_client/proxy.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_PROXY_ #define _DNS_CLIENT_PROXY_ #include "dns_client.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_proxy_handshake(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/query.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/util.h" #include "conn_stream.h" #include "ecs.h" #include "query.h" void _dns_client_query_get(struct dns_query_struct *query) { if (atomic_inc_return(&query->refcnt) <= 0) { BUG("query ref is invalid, domain: %s", query->domain); } } void _dns_client_query_release(struct dns_query_struct *query) { int refcnt = atomic_dec_return(&query->refcnt); unsigned long bucket = 0; struct dns_query_replied *replied_map = NULL; struct hlist_node *tmp = NULL; struct dns_conn_stream *stream = NULL; struct dns_conn_stream *stream_tmp = NULL; if (refcnt) { if (refcnt < 0) { BUG("BUG: refcnt is %d", refcnt); } return; } /* notify caller query end */ if (query->callback) { tlog(TLOG_DEBUG, "result: %s, qtype: %d, has-result: %d, id %d", query->domain, query->qtype, query->has_result, query->sid); query->callback(query->domain, DNS_QUERY_END, NULL, NULL, NULL, 0, query->user_ptr); } pthread_mutex_lock(&query->lock); list_for_each_entry_safe(stream, stream_tmp, &query->conn_stream_list, query_list) { list_del_init(&stream->query_list); stream->query = NULL; _dns_client_conn_stream_put(stream); } pthread_mutex_unlock(&query->lock); pthread_mutex_destroy(&query->lock); /* free resource */ pthread_mutex_lock(&client.domain_map_lock); list_del_init(&query->dns_request_list); hash_del(&query->domain_node); pthread_mutex_unlock(&client.domain_map_lock); hash_for_each_safe(query->replied_map, bucket, tmp, replied_map, node) { hash_del(&replied_map->node); free(replied_map); } memset(query, 0, sizeof(*query)); free(query); } void _dns_client_query_remove(struct dns_query_struct *query) { /* remove query from period check list, and release reference*/ pthread_mutex_lock(&client.domain_map_lock); list_del_init(&query->dns_request_list); hash_del(&query->domain_node); pthread_mutex_unlock(&client.domain_map_lock); _dns_client_query_release(query); } void _dns_client_query_remove_all(void) { struct dns_query_struct *query = NULL; struct dns_query_struct *tmp = NULL; LIST_HEAD(check_list); pthread_mutex_lock(&client.domain_map_lock); list_for_each_entry_safe(query, tmp, &client.dns_request_list, dns_request_list) { list_add(&query->period_list, &check_list); } pthread_mutex_unlock(&client.domain_map_lock); list_for_each_entry_safe(query, tmp, &check_list, period_list) { list_del_init(&query->period_list); _dns_client_query_remove(query); } } int _dns_client_send_query(struct dns_query_struct *query) { unsigned char packet_buff[DNS_PACKSIZE]; unsigned char inpacket[DNS_IN_PACKSIZE]; struct dns_packet *packet = (struct dns_packet *)packet_buff; int encode_len = 0; /* init dns packet head */ struct dns_head head; memset(&head, 0, sizeof(head)); head.id = query->sid; head.qr = DNS_QR_QUERY; head.opcode = DNS_OP_QUERY; head.aa = 0; head.rd = 1; head.ra = 0; head.ad = query->edns0_do; head.rcode = 0; if (dns_packet_init(packet, DNS_PACKSIZE, &head) != 0) { tlog(TLOG_ERROR, "init packet failed."); return -1; } /* add question */ if (dns_add_domain(packet, query->domain, query->qtype, DNS_C_IN) != 0) { tlog(TLOG_ERROR, "add domain to packet failed."); return -1; } dns_set_OPT_payload_size(packet, DNS_IN_PACKSIZE); if (query->edns0_do) { dns_set_OPT_option(packet, DNS_OPT_FLAG_DO); } /* dns_add_OPT_TCP_KEEPALIVE(packet, 1200); */ if (_dns_client_dns_add_ecs(query, packet) != 0) { tlog(TLOG_ERROR, "add ecs failed."); return -1; } /* encode packet */ encode_len = dns_encode(inpacket, DNS_IN_PACKSIZE, packet); if (encode_len <= 0) { tlog(TLOG_ERROR, "encode query failed."); return -1; } if (encode_len > DNS_IN_PACKSIZE) { BUG("size is invalid."); return -1; } /* send query packet */ return _dns_client_send_packet(query, inpacket, encode_len); } struct dns_query_struct *_dns_client_get_request(char *domain, int qtype, unsigned short sid) { struct dns_query_struct *query = NULL; struct dns_query_struct *query_result = NULL; struct hlist_node *tmp = NULL; uint32_t key = 0; /* get query by hash key : id + domain */ key = hash_string(domain); key = jhash(&sid, sizeof(sid), key); key = jhash(&qtype, sizeof(qtype), key); pthread_mutex_lock(&client.domain_map_lock); hash_for_each_possible_safe(client.domain_map, query, tmp, domain_node, key) { if (sid != query->sid) { continue; } if (qtype != query->qtype) { continue; } if (strncmp(query->domain, domain, DNS_MAX_CNAME_LEN) != 0) { continue; } query_result = query; _dns_client_query_get(query_result); break; } pthread_mutex_unlock(&client.domain_map_lock); return query_result; } int _dns_replied_check_add(struct dns_query_struct *dns_query, struct dns_server_info *server) { uint32_t key = 0; struct dns_query_replied *replied_map = NULL; /* avoid multiple replies from one server */ key = jhash((const void *)&server, sizeof(server), 0); hash_for_each_possible(dns_query->replied_map, replied_map, node, key) { /* already replied, ignore this reply */ if (replied_map->server == server) { return -1; } } replied_map = zalloc(1, sizeof(*replied_map)); if (replied_map == NULL) { tlog(TLOG_ERROR, "malloc failed"); return -1; } /* add address info to check hashtable */ replied_map->server = server; hash_add(dns_query->replied_map, &replied_map->node, key); return 0; } void _dns_replied_check_remove(struct dns_query_struct *dns_query, struct dns_server_info *server) { uint32_t key = 0; struct dns_query_replied *replied_map = NULL; key = jhash((const void *)&server, sizeof(server), 0); hash_for_each_possible(dns_query->replied_map, replied_map, node, key) { if (replied_map->server == server) { hash_del(&replied_map->node); free(replied_map); return; } } } int _dns_client_query_parser_options(struct dns_query_struct *query, struct dns_query_options *options) { if (options->enable_flag & DNS_QUEY_OPTION_ECS_IP) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); struct dns_opt_ecs *ecs = NULL; ecs = &query->ecs.ecs; getaddr_by_host(options->ecs_ip.ip, (struct sockaddr *)&addr, &addr_len); query->ecs.enable = 1; ecs->source_prefix = options->ecs_ip.subnet; ecs->scope_prefix = 0; switch (addr.ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&addr; ecs->family = DNS_OPT_ECS_FAMILY_IPV4; memcpy(&ecs->addr, &addr_in->sin_addr.s_addr, 4); } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { memcpy(&ecs->addr, addr_in6->sin6_addr.s6_addr + 12, 4); ecs->family = DNS_OPT_ECS_FAMILY_IPV4; } else { memcpy(&ecs->addr, addr_in6->sin6_addr.s6_addr, 16); ecs->family = DNS_OPT_ECS_FAMILY_IPV6; } } break; default: tlog(TLOG_WARN, "ECS set failure."); break; } } if (options->enable_flag & DNS_QUEY_OPTION_ECS_DNS) { struct dns_opt_ecs *ecs = &options->ecs_dns; if (ecs->family != DNS_OPT_ECS_FAMILY_IPV6 && ecs->family != DNS_OPT_ECS_FAMILY_IPV4) { return -1; } if (ecs->family == DNS_OPT_ECS_FAMILY_IPV4 && ecs->source_prefix > 32) { return -1; } if (ecs->family == DNS_OPT_ECS_FAMILY_IPV6 && ecs->source_prefix > 128) { return -1; } memcpy(&query->ecs.ecs, ecs, sizeof(query->ecs.ecs)); query->ecs.enable = 1; } if (query->ecs.enable == 0) { _dns_client_query_setup_default_ecs(query); } if (options->enable_flag & DNS_QUEY_OPTION_EDNS0_DO) { query->edns0_do = 1; } return 0; } void _dns_client_retry_dns_query(struct dns_query_struct *query) { if (atomic_dec_and_test(&query->retry_count) || (query->has_result != 0)) { _dns_client_query_remove(query); if (query->has_result == 0) { tlog(TLOG_DEBUG, "retry query %s, type: %d, id: %d failed", query->domain, query->qtype, query->sid); } } else { tlog(TLOG_DEBUG, "retry query %s, type: %d, id: %d", query->domain, query->qtype, query->sid); _dns_client_send_query(query); } } int _dns_client_add_hashmap(struct dns_query_struct *query) { uint32_t key = 0; struct hlist_node *tmp = NULL; struct dns_query_struct *query_check = NULL; int is_exists = 0; int loop = 0; while (loop++ <= 32) { if (RAND_bytes((unsigned char *)&query->sid, sizeof(query->sid)) != 1) { query->sid = random(); } key = hash_string(query->domain); key = jhash(&query->sid, sizeof(query->sid), key); key = jhash(&query->qtype, sizeof(query->qtype), key); is_exists = 0; pthread_mutex_lock(&client.domain_map_lock); hash_for_each_possible_safe(client.domain_map, query_check, tmp, domain_node, key) { if (query->sid != query_check->sid) { continue; } if (query->qtype != query_check->qtype) { continue; } if (strncmp(query_check->domain, query->domain, DNS_MAX_CNAME_LEN) != 0) { continue; } is_exists = 1; break; } if (is_exists == 1) { pthread_mutex_unlock(&client.domain_map_lock); continue; } hash_add(client.domain_map, &query->domain_node, key); pthread_mutex_unlock(&client.domain_map_lock); break; } return 0; } ================================================ FILE: src/dns_client/query.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_QUERY_ #define _DNS_CLIENT_QUERY_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_client_retry_dns_query(struct dns_query_struct *query); void _dns_client_query_remove(struct dns_query_struct *query); void _dns_client_query_release(struct dns_query_struct *query); void _dns_client_query_get(struct dns_query_struct *query); void _dns_client_query_remove_all(void); int _dns_client_send_query(struct dns_query_struct *query); struct dns_query_struct *_dns_client_get_request(char *domain, int qtype, unsigned short sid); int _dns_replied_check_add(struct dns_query_struct *dns_query, struct dns_server_info *server); void _dns_replied_check_remove(struct dns_query_struct *dns_query, struct dns_server_info *server); int _dns_client_query_parser_options(struct dns_query_struct *query, struct dns_query_options *options); void _dns_client_retry_dns_query(struct dns_query_struct *query); int _dns_client_add_hashmap(struct dns_query_struct *query); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/server_info.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "server_info.h" #include "client_socket.h" #include "client_tls.h" #include "conn_stream.h" #include "ecs.h" #include "group.h" #include "pending_server.h" #include "smartdns/fast_ping.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include #include #include unsigned int dns_client_server_result_flag(struct dns_server_info *server_info) { if (server_info == NULL) { return 0; } return server_info->flags.result_flag; } const char *dns_client_get_server_ip(struct dns_server_info *server_info) { if (server_info == NULL) { return NULL; } return server_info->ip; } dns_server_security_status dns_client_get_server_security_status(struct dns_server_info *server_info) { if (server_info == NULL) { return DNS_CLIENT_SERVER_SECURITY_UNKNOW; } return server_info->security_status; } const char *dns_client_get_server_host(struct dns_server_info *server_info) { if (server_info == NULL) { return NULL; } return server_info->host; } int dns_client_get_server_port(struct dns_server_info *server_info) { if (server_info == NULL) { return 0; } return server_info->port; } static inline void _dns_server_inc_server_num(struct dns_server_info *server_info) { if (server_info->type == DNS_SERVER_MDNS) { return; } atomic_inc(&client.dns_server_num); } static inline void _dns_server_dec_server_num(struct dns_server_info *server_info) { if (server_info->type == DNS_SERVER_MDNS) { return; } atomic_dec(&client.dns_server_num); } void _dns_server_inc_prohibit_server_num(struct dns_server_info *server_info) { if (server_info->type == DNS_SERVER_MDNS) { return; } atomic_inc(&client.dns_server_prohibit_num); } void _dns_server_dec_prohibit_server_num(struct dns_server_info *server_info) { if (server_info->type == DNS_SERVER_MDNS) { return; } atomic_dec(&client.dns_server_prohibit_num); } dns_server_type_t dns_client_get_server_type(struct dns_server_info *server_info) { if (server_info == NULL) { return DNS_SERVER_TYPE_END; } return server_info->type; } struct dns_server_stats *dns_client_get_server_stats(struct dns_server_info *server_info) { if (server_info == NULL) { return NULL; } return &server_info->stats; } int dns_client_server_is_alive(struct dns_server_info *server_info) { if (server_info == NULL) { return 0; } return atomic_read(&server_info->is_alive); } static void _dns_client_server_free(struct dns_server_info *server_info) { pthread_mutex_lock(&client.server_list_lock); if (!list_empty(&server_info->list)) { list_del_init(&server_info->list); _dns_server_dec_server_num(server_info); } pthread_mutex_unlock(&client.server_list_lock); list_del_init(&server_info->check_list); _dns_client_server_close(server_info); pthread_mutex_destroy(&server_info->lock); free(server_info); } void dns_client_server_info_get(struct dns_server_info *server_info) { if (server_info == NULL) { return; } atomic_inc(&server_info->refcnt); } void dns_client_server_info_release(struct dns_server_info *server_info) { if (server_info == NULL) { return; } int refcnt = atomic_dec_return(&server_info->refcnt); if (refcnt > 0) { return; } _dns_client_server_free(server_info); } static void _dns_client_server_info_remove(struct dns_server_info *server_info) { if (server_info == NULL) { return; } pthread_mutex_lock(&client.server_list_lock); if (!list_empty(&server_info->list)) { list_del_init(&server_info->list); _dns_server_dec_server_num(server_info); } pthread_mutex_unlock(&client.server_list_lock); _dns_client_server_close(server_info); dns_client_server_info_release(server_info); } int dns_client_get_server_info_lists(struct dns_server_info **server_info, int max_server_num) { struct dns_server_info *server = NULL; struct dns_server_info *tmp = NULL; int i = 0; if (server_info == NULL) { return -1; } pthread_mutex_lock(&client.server_list_lock); list_for_each_entry_safe(server, tmp, &client.dns_server_list, list) { if (i >= max_server_num) { break; } server_info[i] = server; dns_client_server_info_get(server_info[i]); i++; } pthread_mutex_unlock(&client.server_list_lock); return i; } /* check whether server exists */ static int _dns_client_server_exist(const char *server_ip, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags) { struct dns_server_info *server_info = NULL; struct dns_server_info *tmp = NULL; pthread_mutex_lock(&client.server_list_lock); list_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list) { if (server_info->port != port || server_info->type != server_type) { continue; } if (memcmp(&server_info->flags, flags, sizeof(*flags)) != 0) { continue; } if (strncmp(server_info->ip, server_ip, DNS_HOSTNAME_LEN) != 0) { continue; } pthread_mutex_unlock(&client.server_list_lock); return 0; } pthread_mutex_unlock(&client.server_list_lock); return -1; } static void _dns_client_server_update_ttl(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result, struct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv, int error, void *userptr) { struct dns_server_info *server_info = userptr; if (result != PING_RESULT_RESPONSE || server_info == NULL) { return; } double rtt = tv->tv_sec * 1000.0 + tv->tv_usec / 1000.0; tlog(TLOG_DEBUG, "from %s: seq=%d ttl=%d time=%.3f\n", host, seqno, ttl, rtt); server_info->ttl = ttl; } /* get server control block by ip and port, type */ struct dns_server_info *_dns_client_get_server(const char *server_ip, int port, dns_server_type_t server_type, const struct client_dns_server_flags *flags) { struct dns_server_info *server_info = NULL; struct dns_server_info *tmp = NULL; struct dns_server_info *server_info_return = NULL; if (server_ip == NULL) { return NULL; } pthread_mutex_lock(&client.server_list_lock); list_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list) { if (server_info->port != port || server_info->type != server_type) { continue; } if (strncmp(server_info->ip, server_ip, DNS_HOSTNAME_LEN) != 0) { continue; } if (memcmp(&server_info->flags, flags, sizeof(*flags)) != 0) { continue; } server_info_return = server_info; break; } pthread_mutex_unlock(&client.server_list_lock); return server_info_return; } /* add dns server information */ int _dns_client_server_add(const char *server_ip, const char *server_host, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags) { struct dns_server_info *server_info = NULL; struct addrinfo *gai = NULL; int spki_data_len = 0; int ttl = 0; char port_s[8]; int sock_type = 0; char skip_check_cert = 0; char ifname[IFNAMSIZ * 2] = {0}; char default_is_alive = 0; switch (server_type) { case DNS_SERVER_UDP: { struct client_dns_server_flag_udp *flag_udp = &flags->udp; ttl = flag_udp->ttl; if (ttl > 255) { ttl = 255; } else if (ttl < -32) { ttl = -32; } sock_type = SOCK_DGRAM; } break; case DNS_SERVER_HTTP3: { struct client_dns_server_flag_https *flag_https = &flags->https; spki_data_len = flag_https->spi_len; if (flag_https->httphost[0] == 0) { if (server_host) { safe_strncpy(flag_https->httphost, server_host, DNS_MAX_CNAME_LEN); } else { set_http_host(server_ip, port, DEFAULT_DNS_HTTPS_PORT, flag_https->httphost); } } sock_type = SOCK_DGRAM; skip_check_cert = flag_https->skip_check_cert; } break; case DNS_SERVER_HTTPS: { struct client_dns_server_flag_https *flag_https = &flags->https; spki_data_len = flag_https->spi_len; if (flag_https->httphost[0] == 0) { if (server_host) { safe_strncpy(flag_https->httphost, server_host, DNS_MAX_CNAME_LEN); } else { set_http_host(server_ip, port, DEFAULT_DNS_HTTPS_PORT, flag_https->httphost); } } sock_type = SOCK_STREAM; skip_check_cert = flag_https->skip_check_cert; } break; case DNS_SERVER_QUIC: { struct client_dns_server_flag_tls *flag_tls = &flags->tls; spki_data_len = flag_tls->spi_len; sock_type = SOCK_DGRAM; skip_check_cert = flag_tls->skip_check_cert; } break; case DNS_SERVER_TLS: { struct client_dns_server_flag_tls *flag_tls = &flags->tls; spki_data_len = flag_tls->spi_len; sock_type = SOCK_STREAM; skip_check_cert = flag_tls->skip_check_cert; } break; case DNS_SERVER_TCP: sock_type = SOCK_STREAM; break; case DNS_SERVER_MDNS: { if (flags->ifname[0] == '\0') { tlog(TLOG_ERROR, "mdns server must set ifname."); return -1; } sock_type = SOCK_DGRAM; default_is_alive = 1; } break; default: return -1; break; } if (spki_data_len > DNS_SERVER_SPKI_LEN) { tlog(TLOG_ERROR, "spki data length is invalid."); return -1; } /* if server exist, return */ if (_dns_client_server_exist(server_ip, port, server_type, flags) == 0) { return 0; } snprintf(port_s, sizeof(port_s), "%d", port); gai = _dns_client_getaddr(server_ip, port_s, sock_type, 0); if (gai == NULL) { tlog(TLOG_DEBUG, "get address failed, %s:%d", server_ip, port); goto errout; } if (server_type != DNS_SERVER_UDP) { flags->result_flag &= (~DNSSERVER_FLAG_CHECK_TTL); } server_info = zalloc(1, sizeof(*server_info)); if (server_info == NULL) { goto errout; } safe_strncpy(server_info->ip, server_ip, sizeof(server_info->ip)); server_info->port = port; server_info->ai_family = gai->ai_family; server_info->ai_addrlen = gai->ai_addrlen; server_info->type = server_type; server_info->fd = -1; server_info->status = DNS_SERVER_STATUS_INIT; server_info->ttl = ttl; server_info->ttl_range = 0; server_info->skip_check_cert = skip_check_cert; server_info->prohibit = 0; server_info->so_mark = flags->set_mark; server_info->drop_packet_latency_ms = flags->drop_packet_latency_ms; server_info->security_status = DNS_CLIENT_SERVER_SECURITY_UNKNOW; atomic_set(&server_info->refcnt, 0); atomic_set(&server_info->is_alive, default_is_alive); INIT_LIST_HEAD(&server_info->check_list); INIT_LIST_HEAD(&server_info->list); safe_strncpy(server_info->proxy_name, flags->proxyname, sizeof(server_info->proxy_name)); if (server_host && server_host[0]) { safe_strncpy(server_info->host, server_host, sizeof(server_info->host)); } else { safe_strncpy(server_info->host, server_ip, sizeof(server_info->host)); } pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&server_info->lock, &attr); pthread_mutexattr_destroy(&attr); memcpy(&server_info->flags, flags, sizeof(server_info->flags)); INIT_LIST_HEAD(&server_info->list); INIT_LIST_HEAD(&server_info->conn_stream_list); if (_dns_client_server_add_ecs(server_info, flags) != 0) { tlog(TLOG_ERROR, "add %s ecs failed.", server_ip); goto errout; } /* exclude this server from default group */ if ((server_info->flags.server_flag & SERVER_FLAG_EXCLUDE_DEFAULT) == 0) { if (_dns_client_add_to_group(DNS_SERVER_GROUP_DEFAULT, server_info) != 0) { tlog(TLOG_ERROR, "add server %s to default group failed.", server_ip); goto errout; } } /* if server type is TLS, create ssl context */ if (server_type == DNS_SERVER_TLS || server_type == DNS_SERVER_HTTPS || server_type == DNS_SERVER_QUIC || server_type == DNS_SERVER_HTTP3) { if (server_type == DNS_SERVER_QUIC || server_type == DNS_SERVER_HTTP3) { server_info->ssl_ctx = _ssl_ctx_get(1); } else { server_info->ssl_ctx = _ssl_ctx_get(0); } if (server_info->ssl_ctx == NULL) { tlog(TLOG_ERROR, "init ssl failed."); goto errout; } if (client.ssl_verify_skip) { server_info->skip_check_cert = 1; } } /* safe address info */ if (gai->ai_addrlen > sizeof(server_info->in6)) { tlog(TLOG_ERROR, "addr len invalid, %d, %zd, %d", gai->ai_addrlen, sizeof(server_info->addr), server_info->ai_family); goto errout; } memcpy(&server_info->addr, gai->ai_addr, gai->ai_addrlen); /* start ping task */ if (server_type == DNS_SERVER_UDP) { if (ttl <= 0 && (server_info->flags.result_flag & DNSSERVER_FLAG_CHECK_TTL)) { server_info->ping_host = fast_ping_start(PING_TYPE_DNS, server_ip, 0, 60000, 1000, _dns_client_server_update_ttl, server_info); if (server_info->ping_host == NULL) { tlog(TLOG_ERROR, "start ping failed."); goto errout; } if (ttl < 0) { server_info->ttl_range = -ttl; } } } /* add to list */ pthread_mutex_lock(&client.server_list_lock); list_add(&server_info->list, &client.dns_server_list); dns_client_server_info_get(server_info); pthread_mutex_unlock(&client.server_list_lock); _dns_server_inc_server_num(server_info); freeaddrinfo(gai); if (flags->ifname[0]) { snprintf(ifname, sizeof(ifname), "@%s", flags->ifname); } tlog(TLOG_INFO, "add server %s:%d%s, type: %s", server_ip, port, ifname, _dns_server_get_type_string(server_info->type)); return 0; errout: if (server_info) { if (server_info->ping_host) { fast_ping_stop(server_info->ping_host); } pthread_mutex_destroy(&server_info->lock); free(server_info); } if (gai) { freeaddrinfo(gai); } return -1; } const char *_dns_server_get_type_string(dns_server_type_t type) { const char *type_str = ""; switch (type) { case DNS_SERVER_UDP: type_str = "udp"; break; case DNS_SERVER_TCP: type_str = "tcp"; break; case DNS_SERVER_TLS: type_str = "tls"; break; case DNS_SERVER_HTTPS: type_str = "https"; break; case DNS_SERVER_MDNS: type_str = "mdns"; break; case DNS_SERVER_HTTP3: type_str = "http3"; break; case DNS_SERVER_QUIC: type_str = "quic"; break; default: break; } return type_str; } void _dns_client_server_close(struct dns_server_info *server_info) { /* stop ping task */ if (server_info->ping_host) { if (fast_ping_stop(server_info->ping_host) != 0) { tlog(TLOG_ERROR, "stop ping failed.\n"); } server_info->ping_host = NULL; } _dns_client_close_socket(server_info); if (server_info->ssl_session) { SSL_SESSION_free(server_info->ssl_session); server_info->ssl_session = NULL; } server_info->ssl_ctx = NULL; } /* remove all servers information */ void _dns_client_server_remove_all(void) { struct dns_server_info *server_info = NULL; struct dns_server_info *tmp = NULL; LIST_HEAD(free_list); pthread_mutex_lock(&client.server_list_lock); list_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list) { list_add(&server_info->check_list, &free_list); dns_client_server_info_get(server_info); } pthread_mutex_unlock(&client.server_list_lock); list_for_each_entry_safe(server_info, tmp, &free_list, check_list) { list_del_init(&server_info->check_list); _dns_client_server_info_remove(server_info); dns_client_server_info_release(server_info); } } /* remove single server */ static int _dns_client_server_remove(const char *server_ip, int port, dns_server_type_t server_type) { struct dns_server_info *server_info = NULL; struct dns_server_info *tmp = NULL; LIST_HEAD(free_list); /* find server and remove */ pthread_mutex_lock(&client.server_list_lock); list_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list) { if (server_info->port != port || server_info->type != server_type) { continue; } if (strncmp(server_info->ip, server_ip, DNS_HOSTNAME_LEN) != 0) { continue; } list_add(&server_info->check_list, &free_list); dns_client_server_info_get(server_info); return 0; } pthread_mutex_unlock(&client.server_list_lock); list_for_each_entry_safe(server_info, tmp, &free_list, check_list) { list_del_init(&server_info->check_list); _dns_client_remove_server_from_groups(server_info); _dns_client_server_info_remove(server_info); dns_client_server_info_release(server_info); } return -1; } void _dns_client_check_servers(void) { struct dns_server_info *server_info = NULL; struct dns_server_info *tmp = NULL; static unsigned int second_count = 0; second_count++; if (second_count % 10 != 0) { return; } pthread_mutex_lock(&client.server_list_lock); list_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list) { dns_stats_server_stats_avg_time_update(&server_info->stats); if (server_info->type != DNS_SERVER_UDP) { continue; } if (server_info->last_send - 600 > server_info->last_recv) { server_info->recv_buff.len = 0; server_info->send_buff.len = 0; tlog(TLOG_DEBUG, "server %s may failure.", server_info->ip); _dns_client_close_socket(server_info); } } pthread_mutex_unlock(&client.server_list_lock); } int dns_client_add_server(const char *server_ip, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags) { return _dns_client_add_server_pending(server_ip, NULL, port, server_type, flags, 1); } int dns_client_remove_server(const char *server_ip, int port, dns_server_type_t server_type) { return _dns_client_server_remove(server_ip, port, server_type); } int dns_server_num(void) { return atomic_read(&client.dns_server_num); } int dns_server_alive_num(void) { return atomic_read(&client.dns_server_num) - atomic_read(&client.dns_server_prohibit_num); } ================================================ FILE: src/dns_client/server_info.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_SERVER_INFO_ #define _DNS_CLIENT_SERVER_INFO_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_server_inc_prohibit_server_num(struct dns_server_info *server_info); void _dns_server_dec_prohibit_server_num(struct dns_server_info *server_info); void _dns_client_server_close(struct dns_server_info *server_info); const char *_dns_server_get_type_string(dns_server_type_t type); void _dns_client_server_remove_all(void); struct dns_server_info *_dns_client_get_server(const char *server_ip, int port, dns_server_type_t server_type, const struct client_dns_server_flags *flags); int _dns_client_server_add(const char *server_ip, const char *server_host, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags); void _dns_client_check_servers(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_client/wake_event.c ================================================ /************************************************************************* * * 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 . */ #include "wake_event.h" #include #include void _dns_client_close_wakeup_event(void) { if (client.fd_wakeup > 0) { close(client.fd_wakeup); client.fd_wakeup = -1; } } void _dns_client_clear_wakeup_event(void) { uint64_t val = 0; int unused __attribute__((unused)); if (client.fd_wakeup <= 0) { return; } unused = read(client.fd_wakeup, &val, sizeof(val)); } void _dns_client_do_wakeup_event(void) { uint64_t val = 1; int unused __attribute__((unused)); if (client.fd_wakeup <= 0) { return; } unused = write(client.fd_wakeup, &val, sizeof(val)); } int _dns_client_create_wakeup_event(void) { int fd_wakeup = -1; fd_wakeup = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (fd_wakeup < 0) { tlog(TLOG_ERROR, "create eventfd failed, %s\n", strerror(errno)); goto errout; } struct epoll_event event; memset(&event, 0, sizeof(event)); event.events = EPOLLIN; event.data.fd = fd_wakeup; if (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd_wakeup, &event) < 0) { tlog(TLOG_ERROR, "add eventfd to epoll failed, %s\n", strerror(errno)); goto errout; } return fd_wakeup; errout: if (fd_wakeup > 0) { close(fd_wakeup); } return -1; } ================================================ FILE: src/dns_client/wake_event.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CLIENT_WAKE_EVENT_ #define _DNS_CLIENT_WAKE_EVENT_ #include "dns_client.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_client_do_wakeup_event(void); int _dns_client_create_wakeup_event(void); void _dns_client_close_wakeup_event(void); void _dns_client_clear_wakeup_event(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/address.c ================================================ /************************************************************************* * * 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 . */ #include "address.h" #include "domain_rule.h" #include "get_domain.h" #include "ptr.h" #include "smartdns/util.h" #define TMP_BUFF_LEN 1024 int _conf_domain_rule_address(char *domain, const char *domain_address) { struct dns_rule_address_IPV4 *address_ipv4 = NULL; struct dns_rule_address_IPV6 *address_ipv6 = NULL; struct dns_rule *address = NULL; char ip[MAX_IP_LEN]; int port = 0; struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); unsigned int flag = 0; char *ptr = NULL; char *field = NULL; char tmpbuff[TMP_BUFF_LEN] = {0}; char ipv6_addr[DNS_MAX_REPLY_IP_NUM][DNS_RR_AAAA_LEN]; int ipv6_num = 0; char ipv4_addr[DNS_MAX_REPLY_IP_NUM][DNS_RR_A_LEN]; int ipv4_num = 0; safe_strncpy(tmpbuff, domain_address, sizeof(tmpbuff)); ptr = tmpbuff; do { field = ptr; if (field == NULL || *field == '\0') { break; } ptr = strstr(ptr, ","); if (ptr) { *ptr = 0; } if (*(field) == '#') { if (strncmp(field, "#4", sizeof("#4")) == 0) { flag = DOMAIN_FLAG_ADDR_IPV4_SOA; } else if (strncmp(field, "#6", sizeof("#6")) == 0) { flag = DOMAIN_FLAG_ADDR_IPV6_SOA; } else if (strncmp(field, "#", sizeof("#")) == 0) { flag = DOMAIN_FLAG_ADDR_SOA; } else { goto errout; } /* add SOA rule */ if (_config_domain_rule_flag_set(domain, flag, 0) != 0) { goto errout; } if (ptr) { ptr++; } continue; } else if (*(field) == '-') { if (strncmp(field, "-4", sizeof("-4")) == 0) { flag = DOMAIN_FLAG_ADDR_IPV4_IGN; } else if (strncmp(field, "-6", sizeof("-6")) == 0) { flag = DOMAIN_FLAG_ADDR_IPV6_IGN; } else if (strncmp(field, "-", sizeof("-")) == 0) { flag = DOMAIN_FLAG_ADDR_IGN; } else { goto errout; } /* ignore rule */ if (_config_domain_rule_flag_set(domain, flag, 0) != 0) { goto errout; } if (ptr) { ptr++; } continue; } /* set address to domain */ if (parse_ip(field, ip, &port) != 0) { goto errout; } addr_len = sizeof(addr); if (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) { goto errout; } switch (addr.ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&addr; if (ipv4_num < DNS_MAX_REPLY_IP_NUM) { memcpy(ipv4_addr[ipv4_num], &addr_in->sin_addr.s_addr, DNS_RR_A_LEN); ipv4_num++; } } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&addr; if (ipv6_num < DNS_MAX_REPLY_IP_NUM) { memcpy(ipv6_addr[ipv6_num], addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN); ipv6_num++; } } break; default: ip[0] = '\0'; break; } /* add PTR */ if (dns_conf.expand_ptr_from_address == 1 && ip[0] != '\0' && _conf_ptr_add(domain, ip, 0) != 0) { goto errout; } if (ptr) { ptr++; } } while (ptr); if (ipv4_num > 0) { address_ipv4 = _new_dns_rule_ext(DOMAIN_RULE_ADDRESS_IPV4, ipv4_num * DNS_RR_A_LEN); if (address_ipv4 == NULL) { goto errout; } memcpy(address_ipv4->ipv4_addr, ipv4_addr[0], ipv4_num * DNS_RR_A_LEN); address_ipv4->addr_num = ipv4_num; address = (struct dns_rule *)address_ipv4; if (_config_domain_rule_add(domain, DOMAIN_RULE_ADDRESS_IPV4, address) != 0) { goto errout; } _dns_rule_put(address); } if (ipv6_num > 0) { address_ipv6 = _new_dns_rule_ext(DOMAIN_RULE_ADDRESS_IPV6, ipv6_num * DNS_RR_AAAA_LEN); if (address_ipv6 == NULL) { goto errout; } memcpy(address_ipv6->ipv6_addr, ipv6_addr[0], ipv6_num * DNS_RR_AAAA_LEN); address_ipv6->addr_num = ipv6_num; address = (struct dns_rule *)address_ipv6; if (_config_domain_rule_add(domain, DOMAIN_RULE_ADDRESS_IPV6, address) != 0) { goto errout; } _dns_rule_put(address); } return 0; errout: if (address) { _dns_rule_put(address); } tlog(TLOG_ERROR, "add address %s, %s at %s:%d failed", domain, domain_address, conf_get_conf_file(), conf_get_current_lineno()); return 0; } int _config_address(void *data, int argc, char *argv[]) { char *value = argv[1]; char domain[DNS_MAX_CONF_CNAME_LEN]; if (argc <= 1) { goto errout; } if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) { goto errout; } return _conf_domain_rule_address(domain, value); errout: tlog(TLOG_ERROR, "add address %s failed", value); return 0; } ================================================ FILE: src/dns_conf/address.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_ADDRESS_H_ #define _DNS_CONF_ADDRESS_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_address(void *data, int argc, char *argv[]); int _conf_domain_rule_address(char *domain, const char *domain_address); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/bind.c ================================================ /************************************************************************* * * 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 . */ #include "bind.h" #include "dns_conf_group.h" #include "domain_rule.h" #include "ipset.h" #include "nftset.h" #include "server_group.h" #include "smartdns/util.h" #include static int _config_bind_ip_parser_nftset(struct dns_bind_ip *bind_ip, unsigned int *server_flag, const char *nftsetname) { struct dns_nftset_rule *nftset_rule = NULL; struct dns_nftset_rule **bind_nftset_rule = NULL; const struct dns_nftset_name *nftset_name = NULL; enum domain_rule type = DOMAIN_RULE_MAX; char *setname = NULL; char *tablename = NULL; char *family = NULL; char copied_name[DNS_MAX_NFTSET_NAMELEN + 1]; safe_strncpy(copied_name, nftsetname, DNS_MAX_NFTSET_NAMELEN); for (char *tok = strtok(copied_name, ","); tok; tok = strtok(NULL, ",")) { char *saveptr = NULL; char *tok_set = NULL; if (strncmp(tok, "#4:", 3U) == 0) { bind_nftset_rule = &bind_ip->nftset_ipset_rule.nftset_ip; type = DOMAIN_RULE_NFTSET_IP; } else if (strncmp(tok, "#6:", 3U) == 0) { bind_nftset_rule = &bind_ip->nftset_ipset_rule.nftset_ip6; type = DOMAIN_RULE_NFTSET_IP6; } else if (strncmp(tok, "-", 2U) == 0) { continue; } else { return -1; } tok_set = tok + 3; if (strncmp(tok_set, "-", 2U) == 0) { *server_flag |= BIND_FLAG_NO_RULE_NFTSET; continue; } family = strtok_r(tok_set, "#", &saveptr); if (family == NULL) { return -1; } tablename = strtok_r(NULL, "#", &saveptr); if (tablename == NULL) { return -1; } setname = strtok_r(NULL, "#", &saveptr); if (setname == NULL) { return -1; } /* new nftset domain */ nftset_name = _dns_conf_get_nftable(family, tablename, setname); if (nftset_name == NULL) { return -1; } nftset_rule = _new_dns_rule(type); if (nftset_rule == NULL) { return -1; } nftset_rule->nfttablename = nftset_name->nfttablename; nftset_rule->nftsetname = nftset_name->nftsetname; nftset_rule->familyname = nftset_name->nftfamilyname; /* reference is 1 here */ *bind_nftset_rule = nftset_rule; nftset_rule = NULL; } return 0; } static int _config_bind_ip_parser_ipset(struct dns_bind_ip *bind_ip, unsigned int *server_flag, const char *ipsetname) { struct dns_ipset_rule **bind_ipset_rule = NULL; struct dns_ipset_rule *ipset_rule = NULL; const char *ipset = NULL; enum domain_rule type = DOMAIN_RULE_MAX; char copied_name[DNS_MAX_NFTSET_NAMELEN + 1]; safe_strncpy(copied_name, ipsetname, DNS_MAX_NFTSET_NAMELEN); for (char *tok = strtok(copied_name, ","); tok; tok = strtok(NULL, ",")) { if (tok[0] == '#') { if (strncmp(tok, "#6:", 3U) == 0) { bind_ipset_rule = &bind_ip->nftset_ipset_rule.ipset_ip6; type = DOMAIN_RULE_IPSET_IPV6; } else if (strncmp(tok, "#4:", 3U) == 0) { bind_ipset_rule = &bind_ip->nftset_ipset_rule.ipset_ip; type = DOMAIN_RULE_IPSET_IPV4; } else { goto errout; } tok += 3; } else { type = DOMAIN_RULE_IPSET; bind_ipset_rule = &bind_ip->nftset_ipset_rule.ipset; } if (strncmp(tok, "-", 1) == 0) { *server_flag |= BIND_FLAG_NO_RULE_IPSET; continue; } if (bind_ipset_rule == NULL) { continue; } /* new ipset domain */ ipset = _dns_conf_get_ipset(tok); if (ipset == NULL) { goto errout; } ipset_rule = _new_dns_rule(type); if (ipset_rule == NULL) { goto errout; } ipset_rule->ipsetname = ipset; /* reference is 1 here */ *bind_ipset_rule = ipset_rule; ipset_rule = NULL; } return 0; errout: if (ipset_rule) { _dns_rule_put(&ipset_rule->head); } return -1; } static int _bind_is_ip_valid(const char *ip) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); char ip_check[MAX_IP_LEN]; int port_check = -1; if (parse_ip(ip, ip_check, &port_check) != 0) { if (port_check != -1 && ip_check[0] == '\0') { return 0; } return -1; } if (getaddr_by_host(ip_check, (struct sockaddr *)&addr, &addr_len) != 0) { return -1; } return 0; } static int _config_bind_ip(int argc, char *argv[], DNS_BIND_TYPE type) { int index = dns_conf.bind_ip_num; struct dns_bind_ip *bind_ip = NULL; char *ip = NULL; int opt = 0; int optind_last = 0; char group_name[DNS_GROUP_NAME_LEN]; const char *group = NULL; unsigned int server_flag = 0; int i = 0; /* clang-format off */ static struct option long_options[] = { {"group", required_argument, NULL, 'g'}, /* add to group */ {"no-rule-addr", no_argument, NULL, 'A'}, {"no-rule-nameserver", no_argument, NULL, 'N'}, {"no-rule-ipset", no_argument, NULL, 'I'}, {"no-rule-sni-proxy", no_argument, NULL, 'P'}, {"no-rule-soa", no_argument, NULL, 'O'}, {"no-speed-check", no_argument, NULL, 'S'}, {"no-cache", no_argument, NULL, 'C'}, {"no-dualstack-selection", no_argument, NULL, 'D'}, {"no-ip-alias", no_argument, NULL, 'a'}, {"force-aaaa-soa", no_argument, NULL, 'F'}, {"acl", no_argument, NULL, 251}, {"no-rules", no_argument, NULL, 252}, {"no-serve-expired", no_argument, NULL, 253}, {"force-https-soa", no_argument, NULL, 254}, {"ipset", required_argument, NULL, 255}, {"nftset", required_argument, NULL, 256}, {"alpn", required_argument, NULL, 257}, {"ddr", no_argument, NULL, 258}, {NULL, no_argument, NULL, 0} }; /* clang-format on */ if (argc <= 1) { tlog(TLOG_ERROR, "bind: invalid parameter."); goto errout; } ip = argv[1]; if (index >= DNS_MAX_BIND_IP) { tlog(TLOG_WARN, "exceeds max server number, %s", ip); return 0; } if (_bind_is_ip_valid(ip) != 0) { tlog(TLOG_ERROR, "bind ip address invalid: %s", ip); return -1; } for (i = 0; i < dns_conf.bind_ip_num; i++) { bind_ip = &dns_conf.bind_ip[i]; if (bind_ip->type != type) { continue; } if (strncmp(bind_ip->ip, ip, DNS_MAX_IPLEN) != 0) { continue; } tlog(TLOG_WARN, "bind server %s, type %d, already configured, skip.", ip, type); return 0; } bind_ip = &dns_conf.bind_ip[index]; bind_ip->type = type; bind_ip->flags = 0; safe_strncpy(bind_ip->ip, ip, DNS_MAX_IPLEN); /* get current group */ if (_config_current_group()) { group = _config_current_group()->group_name; } /* process extra options */ optind = 1; optind_last = 1; while (1) { opt = getopt_long_only(argc, argv, "", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'g': { safe_strncpy(group_name, optarg, DNS_GROUP_NAME_LEN); group = _dns_conf_get_group_name(group_name); break; } case 'A': { server_flag |= BIND_FLAG_NO_RULE_ADDR; break; } case 'a': { server_flag |= BIND_FLAG_NO_IP_ALIAS; break; } case 'N': { server_flag |= BIND_FLAG_NO_RULE_NAMESERVER; break; } case 'I': { server_flag |= BIND_FLAG_NO_RULE_IPSET; break; } case 'P': { server_flag |= BIND_FLAG_NO_RULE_SNIPROXY; break; } case 'S': { server_flag |= BIND_FLAG_NO_SPEED_CHECK; break; } case 'C': { server_flag |= BIND_FLAG_NO_CACHE; break; } case 'O': { server_flag |= BIND_FLAG_NO_RULE_SOA; break; } case 'D': { server_flag |= BIND_FLAG_NO_DUALSTACK_SELECTION; break; } case 'F': { server_flag |= BIND_FLAG_FORCE_AAAA_SOA; break; } case 251: { server_flag |= BIND_FLAG_ACL; break; } case 252: { server_flag |= BIND_FLAG_NO_RULES; break; } case 253: { server_flag |= BIND_FLAG_NO_SERVE_EXPIRED; break; } case 254: { server_flag |= BIND_FLAG_FORCE_HTTPS_SOA; break; } case 255: { _config_bind_ip_parser_ipset(bind_ip, &server_flag, optarg); server_flag |= BIND_FLAG_NO_DUALSTACK_SELECTION; server_flag |= BIND_FLAG_NO_PREFETCH; server_flag |= BIND_FLAG_NO_SERVE_EXPIRED; break; } case 256: { _config_bind_ip_parser_nftset(bind_ip, &server_flag, optarg); server_flag |= BIND_FLAG_NO_DUALSTACK_SELECTION; server_flag |= BIND_FLAG_NO_PREFETCH; server_flag |= BIND_FLAG_NO_SERVE_EXPIRED; break; } case 257: { safe_strncpy(bind_ip->alpn, optarg, DNS_MAX_ALPN_LEN); break; } case 258: { server_flag |= BIND_FLAG_DDR; break; } default: if (optind > optind_last) { tlog(TLOG_WARN, "unknown bind option: %s at '%s:%d'.", argv[optind - 1], conf_get_conf_file(), conf_get_current_lineno()); } break; } optind_last = optind; } /* add new server */ bind_ip->flags = server_flag; bind_ip->group = group; dns_conf.bind_ip_num++; if (bind_ip->type == DNS_BIND_TYPE_TLS || bind_ip->type == DNS_BIND_TYPE_HTTPS) { if (bind_ip->ssl_cert_file == NULL || bind_ip->ssl_cert_key_file == NULL) { bind_ip->ssl_cert_file = dns_conf.bind_ca_file; bind_ip->ssl_cert_key_file = dns_conf.bind_ca_key_file; bind_ip->ssl_cert_key_pass = dns_conf.bind_ca_key_pass; dns_conf.need_cert = 1; } } tlog(TLOG_DEBUG, "bind ip %s, type: %d, flag: %X", ip, type, server_flag); return 0; errout: return -1; } void dns_server_bind_destroy(void) { for (int i = 0; i < dns_conf.bind_ip_num; i++) { struct dns_bind_ip *bind_ip = &dns_conf.bind_ip[i]; if (bind_ip->nftset_ipset_rule.ipset) { _dns_rule_put(&bind_ip->nftset_ipset_rule.ipset->head); } if (bind_ip->nftset_ipset_rule.ipset_ip) { _dns_rule_put(&bind_ip->nftset_ipset_rule.ipset_ip->head); } if (bind_ip->nftset_ipset_rule.ipset_ip6) { _dns_rule_put(&bind_ip->nftset_ipset_rule.ipset_ip6->head); } if (bind_ip->nftset_ipset_rule.nftset_ip) { _dns_rule_put(&bind_ip->nftset_ipset_rule.nftset_ip->head); } if (bind_ip->nftset_ipset_rule.nftset_ip6) { _dns_rule_put(&bind_ip->nftset_ipset_rule.nftset_ip6->head); } } memset(dns_conf.bind_ip, 0, sizeof(dns_conf.bind_ip)); dns_conf.bind_ip_num = 0; } int _config_add_default_server_if_needed(void) { if (dns_conf.bind_ip_num > 0) { return 0; } /* add default server */ char *argv[] = {"bind", "[::]:53", NULL}; int argc = sizeof(argv) / sizeof(char *) - 1; return _config_bind_ip(argc, argv, DNS_BIND_TYPE_UDP); } int _config_bind_ip_udp(void *data, int argc, char *argv[]) { return _config_bind_ip(argc, argv, DNS_BIND_TYPE_UDP); } int _config_bind_ip_tcp(void *data, int argc, char *argv[]) { return _config_bind_ip(argc, argv, DNS_BIND_TYPE_TCP); } int _config_bind_ip_tls(void *data, int argc, char *argv[]) { return _config_bind_ip(argc, argv, DNS_BIND_TYPE_TLS); } int _config_bind_ip_https(void *data, int argc, char *argv[]) { return _config_bind_ip(argc, argv, DNS_BIND_TYPE_HTTPS); } ================================================ FILE: src/dns_conf/bind.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_BIND_H_ #define _DNS_CONF_BIND_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_add_default_server_if_needed(void); int _config_bind_ip_udp(void *data, int argc, char *argv[]); int _config_bind_ip_tcp(void *data, int argc, char *argv[]); int _config_bind_ip_tls(void *data, int argc, char *argv[]); int _config_bind_ip_https(void *data, int argc, char *argv[]); void dns_server_bind_destroy(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/bootstrap_dns.c ================================================ /************************************************************************* * * 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 . */ #include "bootstrap_dns.h" #include "domain_rule.h" #include "nameserver.h" #include "smartdns/util.h" char dns_conf_exist_bootstrap_dns; int _config_update_bootstrap_dns_rule(void) { struct dns_servers *server = NULL; if (dns_conf_exist_bootstrap_dns == 0) { return 0; } for (int i = 0; i < dns_conf.server_num; i++) { server = &dns_conf.servers[i]; if (check_is_ipaddr(server->server) == 0) { continue; } _conf_domain_rule_nameserver(server->server, "bootstrap-dns"); } return 0; } ================================================ FILE: src/dns_conf/bootstrap_dns.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_BOOTSTRAP_H_ #define _DNS_CONF_BOOTSTRAP_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_update_bootstrap_dns_rule(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/client_rule.c ================================================ /************************************************************************* * * 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 . */ #include "client_rule.h" #include "dns_conf_group.h" #include "ip_rule.h" #include "server_group.h" #include "set_file.h" #include "smartdns/util.h" #include static radix_node_t *_create_client_rules_node(const char *addr) { radix_node_t *node = NULL; void *p = NULL; prefix_t prefix; const char *errmsg = NULL; p = prefix_pton(addr, -1, &prefix, &errmsg); if (p == NULL) { return NULL; } node = radix_lookup(dns_conf.client_rule.rule, &prefix); return node; } static void *_new_dns_client_rule_ext(enum client_rule client_rule, int ext_size) { struct dns_client_rule *rule; int size = 0; if (client_rule >= CLIENT_RULE_MAX) { return NULL; } switch (client_rule) { case CLIENT_RULE_FLAGS: size = sizeof(struct client_rule_flags); break; case CLIENT_RULE_GROUP: size = sizeof(struct client_rule_group); break; default: return NULL; } size += ext_size; rule = zalloc(1, size); if (!rule) { return NULL; } rule->rule = client_rule; atomic_set(&rule->refcnt, 1); return rule; } static void *_new_dns_client_rule(enum client_rule client_rule) { return _new_dns_client_rule_ext(client_rule, 0); } static void _dns_client_rule_get(struct dns_client_rule *rule) { atomic_inc(&rule->refcnt); } static void _dns_client_rule_put(struct dns_client_rule *rule) { int refcount = atomic_dec_return(&rule->refcnt); if (refcount > 0) { return; } free(rule); } static int _config_client_rules_free(struct dns_client_rules *client_rules) { int i = 0; if (client_rules == NULL) { return 0; } for (i = 0; i < CLIENT_RULE_MAX; i++) { if (client_rules->rules[i] == NULL) { continue; } _dns_client_rule_put(client_rules->rules[i]); client_rules->rules[i] = NULL; } free(client_rules); return 0; } static struct client_roue_group_mac *_config_client_rule_group_mac_new(uint8_t mac[6]) { struct client_roue_group_mac *group_mac = NULL; uint32_t key; group_mac = zalloc(1, sizeof(*group_mac)); if (group_mac == NULL) { return NULL; } memcpy(group_mac->mac, mac, 6); key = jhash(mac, 6, 0); hash_add(dns_conf.client_rule.mac, &group_mac->node, key); dns_conf.client_rule.mac_num++; return group_mac; } struct client_roue_group_mac *dns_server_rule_group_mac_get(const uint8_t mac[6]) { struct client_roue_group_mac *group_mac = NULL; uint32_t key; key = jhash(mac, 6, 0); hash_for_each_possible(dns_conf.client_rule.mac, group_mac, node, key) { if (memcmp(group_mac->mac, mac, 6) == 0) { return group_mac; } } return NULL; } static struct client_roue_group_mac *_config_client_rule_group_mac_get_or_add(uint8_t mac[6]) { struct client_roue_group_mac *group_mac = dns_server_rule_group_mac_get(mac); if (group_mac == NULL) { group_mac = _config_client_rule_group_mac_new(mac); } return group_mac; } static int _config_client_rule_add(const char *ip_cidr, enum client_rule type, void *rule); static int _config_client_rule_add_callback(const char *ip_cidr, void *priv) { struct dns_set_rule_add_callback_args *args = (struct dns_set_rule_add_callback_args *)priv; return _config_client_rule_add(ip_cidr, args->type, args->rule); } static int _config_client_rule_add(const char *ip_cidr, enum client_rule type, void *rule) { struct dns_client_rules *client_rules = NULL; struct dns_client_rules *add_client_rules = NULL; struct client_roue_group_mac *group_mac = NULL; radix_node_t *node = NULL; if (ip_cidr == NULL) { goto errout; } if (type >= CLIENT_RULE_MAX) { goto errout; } uint8_t mac[6]; int is_mac_address = 0; is_mac_address = parser_mac_address(ip_cidr, mac); if (is_mac_address == 0) { group_mac = _config_client_rule_group_mac_get_or_add(mac); if (group_mac == NULL) { tlog(TLOG_ERROR, "get or add mac %s failed", ip_cidr); goto errout; } client_rules = group_mac->rules; } else { if (strncmp(ip_cidr, "ip-set:", sizeof("ip-set:") - 1) == 0) { struct dns_set_rule_add_callback_args args; args.type = type; args.rule = rule; return _config_ip_rule_set_each(ip_cidr + sizeof("ip-set:") - 1, _config_client_rule_add_callback, &args); } /* Get existing or create domain rule */ node = _create_client_rules_node(ip_cidr); if (node == NULL) { tlog(TLOG_ERROR, "create addr node failed."); goto errout; } client_rules = node->data; } if (client_rules == NULL) { add_client_rules = zalloc(1, sizeof(*add_client_rules)); if (add_client_rules == NULL) { goto errout; } client_rules = add_client_rules; if (is_mac_address == 0) { group_mac->rules = client_rules; } else { node->data = client_rules; } } /* add new rule to domain */ if (client_rules->rules[type]) { _dns_client_rule_put(client_rules->rules[type]); client_rules->rules[type] = NULL; } client_rules->rules[type] = rule; _dns_client_rule_get(rule); return 0; errout: if (add_client_rules) { free(add_client_rules); } tlog(TLOG_ERROR, "add client %s rule failed", ip_cidr); return -1; } static int _config_client_rule_flag_callback(const char *ip_cidr, void *priv) { struct dns_set_rule_flags_callback_args *args = (struct dns_set_rule_flags_callback_args *)priv; return _config_client_rule_flag_set(ip_cidr, args->flags, args->is_clear_flag); } int _config_client_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear) { struct dns_client_rules *client_rules = NULL; struct dns_client_rules *add_client_rules = NULL; struct client_rule_flags *client_rule_flags = NULL; struct client_roue_group_mac *group_mac = NULL; radix_node_t *node = NULL; uint8_t mac[6]; int is_mac_address = 0; is_mac_address = parser_mac_address(ip_cidr, mac); if (is_mac_address == 0) { group_mac = _config_client_rule_group_mac_get_or_add(mac); if (group_mac == NULL) { tlog(TLOG_ERROR, "get or add mac %s failed", ip_cidr); goto errout; } client_rules = group_mac->rules; } else { if (strncmp(ip_cidr, "ip-set:", sizeof("ip-set:") - 1) == 0) { struct dns_set_rule_flags_callback_args args; args.flags = flag; args.is_clear_flag = is_clear; return _config_ip_rule_set_each(ip_cidr + sizeof("ip-set:") - 1, _config_client_rule_flag_callback, &args); } /* Get existing or create domain rule */ node = _create_client_rules_node(ip_cidr); if (node == NULL) { tlog(TLOG_ERROR, "create addr node failed."); goto errout; } client_rules = node->data; } if (client_rules == NULL) { add_client_rules = zalloc(1, sizeof(*add_client_rules)); if (add_client_rules == NULL) { goto errout; } client_rules = add_client_rules; if (is_mac_address == 0) { group_mac->rules = client_rules; } else { node->data = client_rules; } } /* add new rule to domain */ if (client_rules->rules[CLIENT_RULE_FLAGS] == NULL) { client_rule_flags = _new_dns_client_rule(CLIENT_RULE_FLAGS); client_rule_flags->flags = 0; client_rules->rules[CLIENT_RULE_FLAGS] = &client_rule_flags->head; } client_rule_flags = container_of(client_rules->rules[CLIENT_RULE_FLAGS], struct client_rule_flags, head); if (is_clear == false) { client_rule_flags->flags |= flag; } else { client_rule_flags->flags &= ~flag; } client_rule_flags->is_flag_set |= flag; return 0; errout: if (add_client_rules) { free(add_client_rules); } tlog(TLOG_ERROR, "set ip %s flags failed", ip_cidr); return -1; } static void _config_client_rule_iter_free_cb(radix_node_t *node, void *cbctx) { struct dns_client_rules *client_rules = NULL; if (node == NULL) { return; } if (node->data == NULL) { return; } client_rules = node->data; _config_client_rules_free(client_rules); node->data = NULL; } int _config_client_rules(void *data, int argc, char *argv[]) { int opt = 0; int optind_last = 0; const char *client = argv[1]; unsigned int server_flag = 0; const char *group = NULL; /* clang-format off */ static struct option long_options[] = { {"group", required_argument, NULL, 'g'}, {"no-rule-addr", no_argument, NULL, 'A'}, {"no-rule-nameserver", no_argument, NULL, 'N'}, {"no-rule-ipset", no_argument, NULL, 'I'}, {"no-rule-sni-proxy", no_argument, NULL, 'P'}, {"no-rule-soa", no_argument, NULL, 'O'}, {"no-speed-check", no_argument, NULL, 'S'}, {"no-cache", no_argument, NULL, 'C'}, {"no-dualstack-selection", no_argument, NULL, 'D'}, {"no-ip-alias", no_argument, NULL, 'a'}, {"force-aaaa-soa", no_argument, NULL, 'F'}, {"acl", no_argument, NULL, 251}, {"no-rules", no_argument, NULL, 252}, {"no-serve-expired", no_argument, NULL, 253}, {"force-https-soa", no_argument, NULL, 254}, {NULL, no_argument, NULL, 0} }; /* clang-format on */ if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } /* get current group */ if (_config_current_group()) { group = _config_current_group()->group_name; } /* process extra options */ optind = 1; optind_last = 1; while (1) { opt = getopt_long_only(argc, argv, "g:", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'g': { group = optarg; break; } case 'A': { server_flag |= BIND_FLAG_NO_RULE_ADDR; break; } case 'a': { server_flag |= BIND_FLAG_NO_IP_ALIAS; break; } case 'N': { server_flag |= BIND_FLAG_NO_RULE_NAMESERVER; break; } case 'I': { server_flag |= BIND_FLAG_NO_RULE_IPSET; break; } case 'P': { server_flag |= BIND_FLAG_NO_RULE_SNIPROXY; break; } case 'S': { server_flag |= BIND_FLAG_NO_SPEED_CHECK; break; } case 'C': { server_flag |= BIND_FLAG_NO_CACHE; break; } case 'O': { server_flag |= BIND_FLAG_NO_RULE_SOA; break; } case 'D': { server_flag |= BIND_FLAG_NO_DUALSTACK_SELECTION; break; } case 'F': { server_flag |= BIND_FLAG_FORCE_AAAA_SOA; break; } case 251: { server_flag |= BIND_FLAG_ACL; break; } case 252: { server_flag |= BIND_FLAG_NO_RULES; break; } case 253: { server_flag |= BIND_FLAG_NO_SERVE_EXPIRED; break; } case 254: { server_flag |= BIND_FLAG_FORCE_HTTPS_SOA; break; } default: if (optind > optind_last) { tlog(TLOG_WARN, "unknown client-rules option: %s at '%s:%d'.", argv[optind - 1], conf_get_conf_file(), conf_get_current_lineno()); } break; } optind_last = optind; } if (group != NULL) { if (_config_client_rule_group_add(client, group) != 0) { tlog(TLOG_ERROR, "add group rule failed."); goto errout; } } if (_config_client_rule_flag_set(client, server_flag, 0) != 0) { tlog(TLOG_ERROR, "set client rule flags failed."); goto errout; } return 0; errout: return -1; } static void _config_client_rule_destroy_mac(void) { struct hlist_node *tmp = NULL; unsigned int i; struct client_roue_group_mac *group_mac = NULL; hash_for_each_safe(dns_conf.client_rule.mac, i, tmp, group_mac, node) { hlist_del_init(&group_mac->node); _config_client_rules_free(group_mac->rules); free(group_mac); } } void _config_client_rule_destroy(void) { Destroy_Radix(dns_conf.client_rule.rule, _config_client_rule_iter_free_cb, NULL); _config_client_rule_destroy_mac(); } int _config_client_rule_group_add(const char *client, const char *group_name) { struct client_rule_group *client_rule = NULL; const char *group = NULL; client_rule = _new_dns_client_rule(CLIENT_RULE_GROUP); if (client_rule == NULL) { goto errout; } group = _dns_conf_get_group_name(group_name); if (group == NULL) { goto errout; } client_rule->group_name = group; if (_config_client_rule_add(client, CLIENT_RULE_GROUP, client_rule) != 0) { goto errout; } _dns_client_rule_put(&client_rule->head); return 0; errout: if (client_rule != NULL) { _dns_client_rule_put(&client_rule->head); } return -1; } ================================================ FILE: src/dns_conf/client_rule.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_CLIENT_RULE_H_ #define _DNS_CONF_CLIENT_RULE_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_client_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear); int _config_client_rule_group_add(const char *client, const char *group_name); int _config_client_rules(void *data, int argc, char *argv[]); void _config_client_rule_destroy(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/client_subnet.c ================================================ /************************************************************************* * * 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 . */ #include "client_subnet.h" #include "dns_conf_group.h" #include "set_file.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" int _conf_client_subnet(char *subnet, struct dns_edns_client_subnet *ipv4_ecs, struct dns_edns_client_subnet *ipv6_ecs) { char *slash = NULL; int subnet_len = 0; struct dns_edns_client_subnet *ecs = NULL; struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); char str_subnet[128]; if (subnet == NULL) { return -1; } safe_strncpy(str_subnet, subnet, sizeof(str_subnet)); slash = strstr(str_subnet, "/"); if (slash) { *slash = 0; slash++; subnet_len = atoi(slash); } if (getaddr_by_host(str_subnet, (struct sockaddr *)&addr, &addr_len) != 0) { goto errout; } switch (addr.ss_family) { case AF_INET: if (subnet_len < 0 || subnet_len > 32) { return -1; } if (subnet_len == 0) { subnet_len = 32; } ecs = ipv4_ecs; break; case AF_INET6: if (subnet_len < 0 || subnet_len > 128) { return -1; } if (subnet_len == 0) { subnet_len = 128; } ecs = ipv6_ecs; break; default: goto errout; } if (ecs == NULL) { return 0; } safe_strncpy(ecs->ip, str_subnet, DNS_MAX_IPLEN); ecs->subnet = subnet_len; ecs->enable = 1; return 0; errout: return -1; } int _conf_edns_client_subnet(void *data, int argc, char *argv[]) { if (argc <= 1) { return -1; } return _conf_client_subnet(argv[1], &_config_current_rule_group()->ipv4_ecs, &_config_current_rule_group()->ipv6_ecs); } ================================================ FILE: src/dns_conf/client_subnet.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_CLIENT_SUBNET_H_ #define _DNS_CONF_CLIENT_SUBNET_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _conf_edns_client_subnet(void *data, int argc, char *argv[]); int _conf_client_subnet(char *subnet, struct dns_edns_client_subnet *ipv4_ecs, struct dns_edns_client_subnet *ipv6_ecs); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/cname.c ================================================ /************************************************************************* * * 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 . */ #include "cname.h" #include "domain_rule.h" #include "set_file.h" #include "smartdns/lib/stringutil.h" int _conf_domain_rule_cname(const char *domain, const char *cname) { struct dns_cname_rule *cname_rule = NULL; enum domain_rule type = DOMAIN_RULE_CNAME; cname_rule = _new_dns_rule(type); if (cname_rule == NULL) { goto errout; } /* ignore this domain */ if (*cname == '-') { if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_CNAME_IGN, 0) != 0) { goto errout; } return 0; } safe_strncpy(cname_rule->cname, cname, DNS_MAX_CONF_CNAME_LEN); if (_config_domain_rule_add(domain, type, cname_rule) != 0) { goto errout; } _dns_rule_put(&cname_rule->head); cname_rule = NULL; return 0; errout: tlog(TLOG_ERROR, "add cname %s:%s failed", domain, cname); if (cname_rule) { _dns_rule_put(&cname_rule->head); } return 0; } int _config_cname(void *data, int argc, char *argv[]) { char *value = argv[1]; char domain[DNS_MAX_CONF_CNAME_LEN]; if (argc <= 1) { goto errout; } if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) { goto errout; } return _conf_domain_rule_cname(domain, value); errout: tlog(TLOG_ERROR, "add cname %s:%s failed", domain, value); return 0; } ================================================ FILE: src/dns_conf/cname.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_CNAME_H_ #define _DNS_CONF_CNAME_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_cname(void *data, int argc, char *argv[]); int _conf_domain_rule_cname(const char *domain, const char *cname); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/conf_file.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "conf_file.h" #include "dns_conf_group.h" #include "set_file.h" #include "smartdns/lib/conf.h" #include "smartdns/lib/hashtable.h" #include "smartdns/util.h" #include "smartdns/lib/stringutil.h" #include #include #include #include struct conf_file_path { struct hlist_node node; char file[DNS_MAX_PATH]; }; struct hash_table conf_file_table; int conf_file_table_init(void) { hash_table_init(conf_file_table, 8); return 0; } static int conf_file_check_duplicate(const char *conf_file) { struct conf_file_path *file = NULL; uint32_t key = 0; key = hash_string(conf_file); hash_table_for_each_possible(conf_file_table, file, node, key) { if (strncmp(file->file, conf_file, DNS_MAX_PATH) != 0) { continue; } return 0; } file = zalloc(1, sizeof(*file)); if (file == NULL) { return -1; } safe_strncpy(file->file, conf_file, DNS_MAX_PATH); hash_table_add(conf_file_table, &file->node, key); return -1; } static int conf_additional_file(const char *conf_file) { char file_path[PATH_MAX]; char file_path_dir[PATH_MAX]; if (conf_file == NULL) { return -1; } if (conf_file[0] != '/') { safe_strncpy(file_path_dir, conf_get_conf_file(), DNS_MAX_PATH); dir_name(file_path_dir); if (strncmp(file_path_dir, conf_get_conf_file(), sizeof(file_path_dir)) == 0) { if (snprintf(file_path, DNS_MAX_PATH, "%s", conf_file) < 0) { return -1; } } else { if (snprintf(file_path, DNS_MAX_PATH, "%s/%s", file_path_dir, conf_file) < 0) { return -1; } } } else { safe_strncpy(file_path, conf_file, DNS_MAX_PATH); } if (access(file_path, R_OK) != 0) { tlog(TLOG_ERROR, "config file '%s' is not readable, %s", conf_file, strerror(errno)); return -1; } if (conf_file_check_duplicate(file_path) == 0) { return 0; } return load_conf(file_path, smartdns_config_item(), _conf_printf); } static int _config_additional_file_callback(const char *file, void *priv) { return conf_additional_file(file); } int config_additional_file(void *data, int argc, char *argv[]) { const char *conf_pattern = NULL; int opt = 0; const char *group_name = NULL; int ret = 0; struct dns_conf_group_info *last_group_info; if (argc < 1) { return -1; } conf_pattern = argv[1]; if (conf_pattern == NULL) { return -1; } /* clang-format off */ static struct option long_options[] = { {"group", required_argument, NULL, 'g'}, {NULL, no_argument, NULL, 0} }; /* clang-format on */ /* process extra options */ optind = 1; while (1) { opt = getopt_long_only(argc, argv, "g:", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'g': { group_name = optarg; break; } default: break; } } last_group_info = _config_current_group(); if (group_name != NULL) { ret = _config_current_group_push(group_name, NULL); if (ret != 0) { tlog(TLOG_ERROR, "begin group '%s' failed.", group_name); return -1; } } ret = _config_foreach_file(conf_pattern, _config_additional_file_callback, NULL); if (group_name != NULL) { _config_current_group_pop_to(last_group_info); } return ret; } void _config_file_hash_table_destroy(void) { struct conf_file_path *file = NULL; struct hlist_node *tmp = NULL; int i = 0; hash_table_for_each_safe(conf_file_table, i, tmp, file, node) { hlist_del_init(&file->node); free(file); } hash_table_free(conf_file_table, free); } ================================================ FILE: src/dns_conf/conf_file.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_CONF_FILE_H_ #define _DNS_CONF_CONF_FILE_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int conf_file_table_init(void); int config_additional_file(void *data, int argc, char *argv[]); void _config_file_hash_table_destroy(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/ddns_domain.c ================================================ /************************************************************************* * * 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 . */ #include "ddns_domain.h" #include "domain_rule.h" #include "smartdns/lib/stringutil.h" static char ddns_domain[DNS_MAX_CNAME_LEN] = {0}; const char *dns_conf_get_ddns_domain(void) { return ddns_domain; } int _config_ddns_domain(void *data, int argc, char *argv[]) { if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); return -1; } const char *domain = argv[1]; safe_strncpy(ddns_domain, domain, sizeof(ddns_domain)); _config_domain_rule_flag_set(domain, DOMAIN_FLAG_SMARTDNS_DOMAIN, 0); return 0; } ================================================ FILE: src/dns_conf/ddns_domain.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_DDNS_DOMAIN_H_ #define _DNS_CONF_DDNS_DOMAIN_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_ddns_domain(void *data, int argc, char *argv[]); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/dhcp_lease_dnsmasq.c ================================================ /************************************************************************* * * 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 . */ #include "dhcp_lease_dnsmasq.h" #include "host_file.h" #include "ptr.h" #include #include #include #include static char dns_conf_dnsmasq_lease_file[DNS_MAX_PATH]; static time_t dns_conf_dnsmasq_lease_file_time; static int _conf_dhcp_lease_dnsmasq_add(const char *file) { FILE *fp = NULL; char line[MAX_LINE_LEN]; char ip[DNS_MAX_IPLEN]; char hostname[DNS_MAX_CNAME_LEN]; int ret = 0; int line_no = 0; int filed_num = 0; fp = fopen(file, "r"); if (fp == NULL) { tlog(TLOG_WARN, "open file %s error, %s", file, strerror(errno)); return 0; } line_no = 0; while (fgets(line, MAX_LINE_LEN, fp)) { line_no++; filed_num = sscanf(line, "%*s %*s %63s %255s %*s", ip, hostname); if (filed_num <= 0) { continue; } if (strncmp(hostname, "*", DNS_MAX_CNAME_LEN - 1) == 0) { continue; } ret = _conf_host_add(hostname, ip, DNS_HOST_TYPE_DNSMASQ, 1); if (ret != 0) { tlog(TLOG_WARN, "add host %s/%s at %d failed", hostname, ip, line_no); } ret = _conf_ptr_add(hostname, ip, 1); if (ret != 0) { tlog(TLOG_WARN, "add ptr %s/%s at %d failed.", hostname, ip, line_no); } } fclose(fp); return 0; } int _conf_dhcp_lease_dnsmasq_file(void *data, int argc, char *argv[]) { struct stat statbuf; if (argc < 1) { return -1; } conf_get_conf_fullpath(argv[1], dns_conf_dnsmasq_lease_file, sizeof(dns_conf_dnsmasq_lease_file)); if (_conf_dhcp_lease_dnsmasq_add(dns_conf_dnsmasq_lease_file) != 0) { return -1; } if (stat(dns_conf_dnsmasq_lease_file, &statbuf) != 0) { return 0; } dns_conf_dnsmasq_lease_file_time = statbuf.st_mtime; return 0; } int dns_server_check_update_hosts(void) { struct stat statbuf; time_t now = 0; if (dns_conf_dnsmasq_lease_file[0] == '\0') { return -1; } if (stat(dns_conf_dnsmasq_lease_file, &statbuf) != 0) { return -1; } if (dns_conf_dnsmasq_lease_file_time == statbuf.st_mtime) { return -1; } time(&now); if (now - statbuf.st_mtime < 30) { return -1; } _config_ptr_table_destroy(1); _config_host_table_destroy(1); if (_conf_dhcp_lease_dnsmasq_add(dns_conf_dnsmasq_lease_file) != 0) { return -1; } dns_conf_dnsmasq_lease_file_time = statbuf.st_mtime; return 0; } ================================================ FILE: src/dns_conf/dhcp_lease_dnsmasq.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_DHCP_LEASE_DNSMASQ_H_ #define _DNS_CONF_DHCP_LEASE_DNSMASQ_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _conf_dhcp_lease_dnsmasq_file(void *data, int argc, char *argv[]); int dns_server_check_update_hosts(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/dns64.c ================================================ /************************************************************************* * * 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 . */ #include "dns64.h" #include "dns_conf_group.h" #include "domain_rule.h" int _config_dns64(void *data, int argc, char *argv[]) { prefix_t prefix; char *subnet = NULL; const char *errmsg = NULL; void *p = NULL; if (argc <= 1) { return -1; } subnet = argv[1]; if (strncmp(subnet, "-", 2U) == 0) { memset(&_config_current_rule_group()->dns_dns64, 0, sizeof(struct dns_dns64)); return 0; } p = prefix_pton(subnet, -1, &prefix, &errmsg); if (p == NULL) { goto errout; } if (prefix.family != AF_INET6) { tlog(TLOG_ERROR, "dns64 subnet %s is not ipv6", subnet); goto errout; } if (prefix.bitlen <= 0 || prefix.bitlen > 96) { tlog(TLOG_ERROR, "dns64 subnet %s is not valid", subnet); goto errout; } struct dns_dns64 *dns64 = &(_config_current_rule_group()->dns_dns64); memcpy(&dns64->prefix, &prefix.add.sin6.s6_addr, sizeof(dns64->prefix)); dns64->prefix_len = prefix.bitlen; return 0; errout: return -1; } static void _dns_conf_dns64_setup_ipv4only_arpa_rule(void) { _config_domain_rule_flag_set(DNS64_IPV4ONLY_APRA_DOMAIN, DOMAIN_FLAG_DUALSTACK_SELECT, 0); _conf_domain_rule_speed_check(DNS64_IPV4ONLY_APRA_DOMAIN, "none"); _conf_domain_rule_response_mode(DNS64_IPV4ONLY_APRA_DOMAIN, "fastest-response"); } void _dns_conf_dns64_post(void) { _dns_conf_dns64_setup_ipv4only_arpa_rule(); } ================================================ FILE: src/dns_conf/dns64.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_DNS64_H_ #define _DNS_CONF_DNS64_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_dns64(void *data, int argc, char *argv[]); void _dns_conf_dns64_post(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/dns_conf.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/dns_conf.h" #include "smartdns/lib/idna.h" #include "smartdns/tlog.h" #include "smartdns/util.h" #include "address.h" #include "bind.h" #include "bootstrap_dns.h" #include "client_rule.h" #include "client_subnet.h" #include "cname.h" #include "conf_file.h" #include "ddns_domain.h" #include "dhcp_lease_dnsmasq.h" #include "dns64.h" #include "dns_conf_group.h" #include "domain_rule.h" #include "domain_set.h" #include "group.h" #include "host_file.h" #include "https_record.h" #include "ip_alias.h" #include "ip_rule.h" #include "ip_set.h" #include "ipset.h" #include "local_domain.h" #include "nameserver.h" #include "nftset.h" #include "plugin.h" #include "proxy_names.h" #include "proxy_server.h" #include "ptr.h" #include "qtype_soa.h" #include "server.h" #include "server_group.h" #include "smartdns_domain.h" #include "speed_check_mode.h" #include "srv_record.h" #include #include #include #include #include #include static struct config_enum_list dns_conf_response_mode_enum[] = { {"first-ping", DNS_RESPONSE_MODE_FIRST_PING_IP}, {"fastest-ip", DNS_RESPONSE_MODE_FASTEST_IP}, {"fastest-response", DNS_RESPONSE_MODE_FASTEST_RESPONSE}, {NULL, 0}}; struct dns_config dns_conf; struct config_enum_list *response_mode_list(void) { return dns_conf_response_mode_enum; } static int _config_option_parser_filepath(void *data, int argc, char *argv[]) { if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); return -1; } conf_get_conf_fullpath(argv[1], data, DNS_MAX_PATH); return 0; } static int _config_log_level(void *data, int argc, char *argv[]) { /* read log level and set */ char *value = argv[1]; if (strncasecmp("debug", value, MAX_LINE_LEN) == 0) { dns_conf.log_level = TLOG_DEBUG; } else if (strncasecmp("info", value, MAX_LINE_LEN) == 0) { dns_conf.log_level = TLOG_INFO; } else if (strncasecmp("notice", value, MAX_LINE_LEN) == 0) { dns_conf.log_level = TLOG_NOTICE; } else if (strncasecmp("warn", value, MAX_LINE_LEN) == 0) { dns_conf.log_level = TLOG_WARN; } else if (strncasecmp("error", value, MAX_LINE_LEN) == 0) { dns_conf.log_level = TLOG_ERROR; } else if (strncasecmp("fatal", value, MAX_LINE_LEN) == 0) { dns_conf.log_level = TLOG_FATAL; } else if (strncasecmp("off", value, MAX_LINE_LEN) == 0) { dns_conf.log_level = TLOG_OFF; } else { return -1; } return 0; } static int _dns_conf_setup_mdns(void) { if (dns_conf.mdns_lookup != 1) { return 0; } return _conf_domain_rule_nameserver(DNS_SERVER_GROUP_LOCAL, DNS_SERVER_GROUP_MDNS); } static int _config_server_name(void *data, int argc, char *argv[]) { if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); return -1; } utf8_to_punycode(argv[1], strlen(argv[1]), dns_conf.server_name, DNS_MAX_SERVER_NAME_LEN); return 0; } static struct config_item _config_item[] = { CONF_CUSTOM("server-name", _config_server_name, NULL), CONF_YESNO("resolv-hostname", &dns_conf.resolv_hostname), CONF_CUSTOM("bind", _config_bind_ip_udp, NULL), CONF_CUSTOM("bind-tcp", _config_bind_ip_tcp, NULL), CONF_CUSTOM("bind-tls", _config_bind_ip_tls, NULL), CONF_CUSTOM("bind-https", _config_bind_ip_https, NULL), CONF_CUSTOM("bind-cert-root-key-file", _config_option_parser_filepath, &dns_conf.bind_root_ca_key_file), CONF_INT("bind-cert-validity-days", &dns_conf.bind_ca_validity_days, 0, 9999), CONF_CUSTOM("bind-cert-file", _config_option_parser_filepath, &dns_conf.bind_ca_file), CONF_CUSTOM("bind-cert-key-file", _config_option_parser_filepath, &dns_conf.bind_ca_key_file), CONF_STRING("bind-cert-key-pass", dns_conf.bind_ca_key_pass, DNS_MAX_PATH), CONF_CUSTOM("server", _config_server_udp, NULL), CONF_CUSTOM("server-tcp", _config_server_tcp, NULL), CONF_CUSTOM("server-tls", _config_server_tls, NULL), CONF_CUSTOM("server-https", _config_server_https, NULL), CONF_CUSTOM("server-h3", _config_server_http3, NULL), CONF_CUSTOM("server-http3", _config_server_http3, NULL), CONF_CUSTOM("server-quic", _config_server_quic, NULL), CONF_YESNO("mdns-lookup", &dns_conf.mdns_lookup), CONF_YESNO("local-ptr-enable", &dns_conf.local_ptr_enable), CONF_CUSTOM("nameserver", _config_nameserver, NULL), CONF_YESNO("expand-ptr-from-address", &dns_conf.expand_ptr_from_address), CONF_CUSTOM("address", _config_address, NULL), CONF_CUSTOM("cname", _config_cname, NULL), CONF_CUSTOM("srv-record", _config_srv_record, NULL), CONF_CUSTOM("https-record", _config_https_record, NULL), CONF_CUSTOM("proxy-server", _config_proxy_server, NULL), CONF_YESNO_FUNC("ipset-timeout", _dns_conf_group_yesno, group_member(ipset_nftset.ipset_timeout_enable)), CONF_CUSTOM("ipset", _config_ipset, NULL), CONF_CUSTOM("ipset-no-speed", _config_ipset_no_speed, NULL), CONF_YESNO_FUNC("nftset-timeout", _dns_conf_group_yesno, group_member(ipset_nftset.nftset_timeout_enable)), CONF_YESNO("nftset-debug", &dns_conf.nftset_debug_enable), CONF_CUSTOM("nftset", _config_nftset, NULL), CONF_CUSTOM("nftset-no-speed", _config_nftset_no_speed, NULL), CONF_CUSTOM("speed-check-mode", _config_speed_check_mode, NULL), CONF_INT("tcp-idle-time", &dns_conf.tcp_idle_time, 0, 3600), CONF_SSIZE("cache-size", &dns_conf.cachesize, -1, CONF_INT_MAX), CONF_SSIZE("cache-mem-size", &dns_conf.cache_max_memsize, 0, CONF_INT_MAX), CONF_CUSTOM("cache-file", _config_option_parser_filepath, (char *)&dns_conf.cache_file), CONF_CUSTOM("data-dir", _config_option_parser_filepath, (char *)&dns_conf.data_dir), CONF_YESNO("cache-persist", &dns_conf.cache_persist), CONF_INT("cache-checkpoint-time", &dns_conf.cache_checkpoint_time, 0, 3600 * 24 * 7), CONF_YESNO_FUNC("prefetch-domain", _dns_conf_group_yesno, group_member(dns_prefetch)), CONF_YESNO_FUNC("serve-expired", _dns_conf_group_yesno, group_member(dns_serve_expired)), CONF_INT_FUNC("serve-expired-ttl", _dns_conf_group_int, group_member(dns_serve_expired_ttl), 0, CONF_INT_MAX), CONF_INT_FUNC("serve-expired-reply-ttl", _dns_conf_group_int, group_member(dns_serve_expired_reply_ttl), 0, CONF_INT_MAX), CONF_INT_FUNC("serve-expired-prefetch-time", _dns_conf_group_int, group_member(dns_serve_expired_prefetch_time), 0, CONF_INT_MAX), CONF_YESNO_FUNC("dualstack-ip-selection", _dns_conf_group_yesno, group_member(dualstack_ip_selection)), CONF_YESNO_FUNC("dualstack-ip-allow-force-AAAA", _dns_conf_group_yesno, group_member(dns_dualstack_ip_allow_force_AAAA)), CONF_INT_FUNC("dualstack-ip-selection-threshold", _dns_conf_group_int, group_member(dns_dualstack_ip_selection_threshold), 0, 1000), CONF_CUSTOM("dns64", _config_dns64, NULL), CONF_CUSTOM("log-level", _config_log_level, NULL), CONF_CUSTOM("log-file", _config_option_parser_filepath, (char *)dns_conf.log_file), CONF_SIZE("log-size", &dns_conf.log_size, 0, 1024 * 1024 * 1024), CONF_INT("log-num", &dns_conf.log_num, 0, 1024), CONF_YESNO("log-color", &dns_conf.log_color_mode), CONF_YESNO("log-console", &dns_conf.log_console), CONF_YESNO("log-syslog", &dns_conf.log_syslog), CONF_INT_BASE("log-file-mode", &dns_conf.log_file_mode, 0, 511, 8), CONF_YESNO("audit-enable", &dns_conf.audit_enable), CONF_YESNO("audit-SOA", &dns_conf.audit_log_SOA), CONF_CUSTOM("audit-file", _config_option_parser_filepath, (char *)&dns_conf.audit_file), CONF_INT_BASE("audit-file-mode", &dns_conf.audit_file_mode, 0, 511, 8), CONF_SIZE("audit-size", &dns_conf.audit_size, 0, 1024 * 1024 * 1024), CONF_INT("audit-num", &dns_conf.audit_num, 0, 1024), CONF_YESNO("audit-console", &dns_conf.audit_console), CONF_YESNO("audit-syslog", &dns_conf.audit_syslog), CONF_YESNO("acl-enable", &dns_conf.acl_enable), CONF_INT_FUNC("rr-ttl", _dns_conf_group_int, group_member(dns_rr_ttl), 0, CONF_INT_MAX), CONF_INT_FUNC("rr-ttl-min", _dns_conf_group_int, group_member(dns_rr_ttl_min), 0, CONF_INT_MAX), CONF_INT_FUNC("rr-ttl-max", _dns_conf_group_int, group_member(dns_rr_ttl_max), 0, CONF_INT_MAX), CONF_INT_FUNC("rr-ttl-reply-max", _dns_conf_group_int, group_member(dns_rr_ttl_reply_max), 0, CONF_INT_MAX), CONF_INT_FUNC("local-ttl", _dns_conf_group_int, group_member(dns_local_ttl), 0, CONF_INT_MAX), CONF_INT_FUNC("max-reply-ip-num", _dns_conf_group_int, group_member(dns_max_reply_ip_num), 1, CONF_INT_MAX), CONF_INT("max-query-limit", &dns_conf.max_query_limit, 0, CONF_INT_MAX), CONF_ENUM_FUNC("response-mode", _dns_conf_group_enum, group_member(dns_response_mode), &dns_conf_response_mode_enum), CONF_YESNO_FUNC("force-AAAA-SOA", _dns_conf_group_yesno, group_member(force_AAAA_SOA)), CONF_YESNO_FUNC("force-no-CNAME", _dns_conf_group_yesno, group_member(dns_force_no_cname)), CONF_CUSTOM("force-qtype-SOA", _config_qtype_soa, NULL), CONF_CUSTOM("blacklist-ip", _config_blacklist_ip, NULL), CONF_CUSTOM("whitelist-ip", _config_whitelist_ip, NULL), CONF_CUSTOM("ip-alias", _config_ip_alias, NULL), CONF_CUSTOM("ip-rules", _config_ip_rules, NULL), CONF_CUSTOM("ip-set", _config_ip_set, NULL), CONF_CUSTOM("bogus-nxdomain", _config_bogus_nxdomain, NULL), CONF_CUSTOM("ignore-ip", _config_ip_ignore, NULL), CONF_CUSTOM("edns-client-subnet", _conf_edns_client_subnet, NULL), CONF_CUSTOM("domain-rules", _config_domain_rules, NULL), CONF_CUSTOM("domain-set", _config_domain_set, NULL), CONF_CUSTOM("ddns-domain", _config_ddns_domain, NULL), CONF_CUSTOM("local-domain", _config_local_domain, NULL), CONF_CUSTOM("dnsmasq-lease-file", _conf_dhcp_lease_dnsmasq_file, NULL), CONF_CUSTOM("hosts-file", _config_hosts_file, NULL), CONF_CUSTOM("group-begin", _config_group_begin, NULL), CONF_CUSTOM("group-end", _config_group_end, NULL), CONF_CUSTOM("group-match", _config_group_match, NULL), CONF_CUSTOM("client-rules", _config_client_rules, NULL), CONF_STRING("ca-file", (char *)&dns_conf.ca_file, DNS_MAX_PATH), CONF_STRING("ca-path", (char *)&dns_conf.ca_path, DNS_MAX_PATH), CONF_STRING("user", (char *)&dns_conf.user, sizeof(dns_conf.user)), CONF_YESNO("debug-save-fail-packet", &dns_conf.dns_save_fail_packet), CONF_YESNO("no-pidfile", &dns_conf.dns_no_pidfile), CONF_YESNO("no-daemon", &dns_conf.dns_no_daemon), CONF_YESNO("restart-on-crash", &dns_conf.dns_restart_on_crash), CONF_SIZE("socket-buff-size", &dns_conf.dns_socket_buff_size, 0, 1024 * 1024 * 8), CONF_CUSTOM("plugin", _config_plugin, NULL), CONF_STRING("resolv-file", (char *)&dns_conf.dns_resolv_file, sizeof(dns_conf.dns_resolv_file)), CONF_STRING("debug-save-fail-packet-dir", (char *)&dns_conf.dns_save_fail_packet_dir, sizeof(dns_conf.dns_save_fail_packet_dir)), CONF_CUSTOM("conf-file", config_additional_file, NULL), CONF_END(), }; const struct config_item *smartdns_config_item(void) { return _config_item; } static int _conf_value_handler(const char *key, const char *value) { if (strstr(key, ".") == NULL) { return -1; } #ifdef BUILD_STATIC return -1; #endif return _config_plugin_conf_add(key, value); } int _conf_printf(const char *key, const char *value, const char *file, int lineno, int ret) { switch (ret) { case CONF_RET_ERR: case CONF_RET_WARN: case CONF_RET_BADCONF: tlog(TLOG_WARN, "process config failed at '%s:%d'.", file, lineno); return -1; break; case CONF_RET_NOENT: if (_conf_value_handler(key, value) == 0) { return 0; } tlog(TLOG_WARN, "unsupported config at '%s:%d'.", file, lineno); return 0; break; default: break; } return 0; } const char *dns_conf_get_cache_dir(void) { if (dns_conf.cache_file[0] == '\0') { return SMARTDNS_CACHE_FILE; } return dns_conf.cache_file; } const char *dns_conf_get_data_dir(void) { if (dns_conf.data_dir[0] == '\0') { return SMARTDNS_DATA_DIR; } return dns_conf.data_dir; } static int _dns_server_load_conf_init(void) { dns_conf.client_rule.rule = New_Radix(); if (dns_conf.client_rule.rule == NULL) { tlog(TLOG_WARN, "init client rule radix tree failed."); return -1; } hash_init(dns_conf.client_rule.mac); conf_file_table_init(); _config_rule_group_init(); _config_ipset_init(); _config_group_table_init(); _config_host_table_init(); _config_ptr_table_init(); _config_domain_set_name_table_init(); _config_ip_set_name_table_init(); _config_plugin_table_init(); if (_config_current_group_push_default() != 0) { tlog(TLOG_ERROR, "init default group failed."); return -1; } return 0; } void dns_server_load_exit(void) { _config_rule_group_destroy(); _config_client_rule_destroy(); _config_ipset_table_destroy(); _config_nftset_table_destroy(); _config_group_table_destroy(); _config_ptr_table_destroy(0); _config_host_table_destroy(0); _config_proxy_table_destroy(); _config_plugin_table_destroy(); _config_plugin_table_conf_destroy(); dns_conf.server_num = 0; dns_server_bind_destroy(); if (dns_conf.log_syslog == 1 || dns_conf.audit_syslog == 1) { closelog(); } memset(&dns_conf, 0, sizeof(dns_conf)); } static void _dns_conf_default_value_init(void) { DOMAIN_CHECK_TYPE tcp_check_type = DOMAIN_CHECK_TCP; if (dns_has_raw_cap) { /* use tcp-syn as default if have raw socket capability */ tcp_check_type = DOMAIN_CHECK_TCP_SYN; } dns_conf.max_query_limit = DNS_MAX_QUERY_LIMIT; dns_conf.tcp_idle_time = 120; dns_conf.local_ptr_enable = 1; dns_conf.audit_size = 1024 * 1024; dns_conf.cache_checkpoint_time = DNS_DEFAULT_CHECKPOINT_TIME; dns_conf.cache_persist = 2; dns_conf.log_num = 8; dns_conf.log_size = 1024 * 128; dns_conf.log_level = TLOG_ERROR; dns_conf.log_color_mode = 1; dns_conf.audit_num = 2; dns_conf.audit_file_mode = 0640; dns_conf.audit_size = 1024 * 128; dns_conf.resolv_hostname = 1; dns_conf.cachesize = -1; dns_conf.cache_max_memsize = -1; dns_conf.default_check_orders.orders[0].type = DOMAIN_CHECK_ICMP; dns_conf.default_check_orders.orders[0].tcp_port = 0; dns_conf.default_check_orders.orders[1].type = tcp_check_type; dns_conf.default_check_orders.orders[1].tcp_port = 80; dns_conf.default_check_orders.orders[2].type = tcp_check_type; dns_conf.default_check_orders.orders[2].tcp_port = 443; dns_conf.default_response_mode = DNS_RESPONSE_MODE_FIRST_PING_IP; } static int _dns_conf_load_pre(void) { _dns_conf_default_value_init(); if (_dns_server_load_conf_init() != 0) { goto errout; } _dns_ping_cap_check(); safe_strncpy(dns_conf.dns_save_fail_packet_dir, SMARTDNS_DEBUG_DIR, sizeof(dns_conf.dns_save_fail_packet_dir)); return 0; errout: return -1; } static void _dns_conf_auto_set_cache_size(void) { uint64_t memsize = get_system_mem_size(); if (dns_conf.cachesize >= 0) { return; } if (memsize <= 16 * 1024 * 1024) { dns_conf.cachesize = 4096; /* 2MB memory */ } else if (memsize <= 32 * 1024 * 1024) { dns_conf.cachesize = 16384; /* 8MB memory*/ } else if (memsize <= 64 * 1024 * 1024) { dns_conf.cachesize = 32768; /* 16MB memory*/ } else if (memsize <= 128 * 1024 * 1024) { dns_conf.cachesize = 65536; /* 32MB memory*/ } else if (memsize <= 256 * 1024 * 1024) { dns_conf.cachesize = 131072; /* 64MB memory*/ } else if (memsize <= 512LL * 1024 * 1024) { dns_conf.cachesize = 196608; /* 96MB memory*/ } else if (memsize <= 1024LL * 1024 * 1024) { dns_conf.cachesize = 262144; /* 128MB memory*/ } else if (memsize <= 2048LL * 1024 * 1024) { dns_conf.cachesize = 393216; /* 192MB memory*/ } else if (memsize <= 4096LL * 1024 * 1024) { dns_conf.cachesize = 524288; /* 256MB memory*/ } else { dns_conf.cachesize = 1048576; /* 512MB memory*/ } } static int _dns_conf_load_post(void) { _config_current_group_pop_to_default(); _config_setup_smartdns_domain(); _dns_conf_speed_check_mode_verify(); _dns_conf_auto_set_cache_size(); _dns_conf_setup_mdns(); if (dns_conf.dns_resolv_file[0] == '\0') { safe_strncpy(dns_conf.dns_resolv_file, DNS_RESOLV_FILE, sizeof(dns_conf.dns_resolv_file)); } _dns_conf_group_post(); _dns_conf_dns64_post(); _config_domain_set_name_table_destroy(); _config_ip_set_name_table_destroy(); _config_update_bootstrap_dns_rule(); _config_add_default_server_if_needed(); _config_file_hash_table_destroy(); _config_current_group_pop_all(); if (dns_conf.log_syslog == 0 && dns_conf.audit_syslog == 0) { closelog(); } return 0; } int dns_server_load_conf(const char *file) { int ret = 0; ret = _dns_conf_load_pre(); if (ret != 0) { return ret; } openlog("smartdns", LOG_CONS, LOG_USER); ret = load_conf(file, _config_item, _conf_printf); if (ret != 0) { closelog(); return ret; } ret = _dns_conf_load_post(); return ret; } ================================================ FILE: src/dns_conf/dns_conf.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_H_ #define _DNS_CONF_H_ #include "smartdns/dns_conf.h" #include "smartdns/tlog.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ const struct config_item *smartdns_config_item(void); int _conf_printf(const char *key, const char *value, const char *file, int lineno, int ret); struct config_enum_list *response_mode_list(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/dns_conf_group.c ================================================ /************************************************************************* * * 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 . */ #include "dns_conf_group.h" #include "domain_rule.h" #include "ip_rule.h" #include "server_group.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" struct dns_conf_group_info *dns_conf_current_group_info; struct dns_conf_group_info *dns_conf_default_group_info; static LIST_HEAD(dns_conf_group_info_list); struct dns_conf_rule dns_conf_rule; int _config_rule_group_init(void) { hash_init(dns_conf_rule.group); dns_conf_rule.default_conf = _config_rule_group_new(""); if (dns_conf_rule.default_conf == NULL) { tlog(TLOG_WARN, "init default domain rule failed."); return -1; } return 0; } __attribute__((unused)) int _dns_conf_group_int(int value, int *data) { struct dns_conf_group *conf_group = _config_current_rule_group(); if (conf_group == NULL) { return -1; } void *ptr = (char *)conf_group + (size_t)data; *(int *)ptr = value; return 0; } __attribute__((unused)) int _dns_conf_group_int_base(int value, int *data) { struct dns_conf_group *conf_group = _config_current_rule_group(); if (conf_group == NULL) { return -1; } void *ptr = (char *)conf_group + (size_t)data; *(int *)ptr = value; return 0; } __attribute__((unused)) int _dns_conf_group_string(const char *value, char *data) { struct dns_conf_group *conf_group = _config_current_rule_group(); if (conf_group == NULL) { return -1; } char *ptr = (char *)conf_group + (size_t)data; safe_strncpy(ptr, value, DNS_MAX_PATH); return 0; } __attribute__((unused)) int _dns_conf_group_yesno(int value, int *data) { struct dns_conf_group *conf_group = _config_current_rule_group(); if (conf_group == NULL) { return -1; } void *ptr = (char *)conf_group + (size_t)data; *(int *)ptr = value; return 0; } __attribute__((unused)) int _dns_conf_group_size(size_t value, size_t *data) { struct dns_conf_group *conf_group = _config_current_rule_group(); if (conf_group == NULL) { return -1; } void *ptr = (char *)conf_group + (size_t)data; *(size_t *)ptr = value; return 0; } __attribute__((unused)) int _dns_conf_group_ssize(ssize_t value, ssize_t *data) { struct dns_conf_group *conf_group = _config_current_rule_group(); if (conf_group == NULL) { return -1; } void *ptr = (char *)conf_group + (size_t)data; *(ssize_t *)ptr = value; return 0; } __attribute__((unused)) int _dns_conf_group_enum(int value, int *data) { struct dns_conf_group *conf_group = _config_current_rule_group(); if (conf_group == NULL) { return -1; } void *ptr = (char *)conf_group + (size_t)data; *(int *)ptr = value; return 0; } struct dns_conf_group *_config_current_rule_group(void) { if (dns_conf_current_group_info == NULL) { return NULL; } return dns_conf_current_group_info->rule; } struct dns_conf_group_info *_config_current_group(void) { return dns_conf_current_group_info; } void _config_set_current_group(struct dns_conf_group_info *group_info) { if (group_info == NULL) { return; } dns_conf_current_group_info = group_info; } struct dns_conf_group_info *_config_default_group(void) { return dns_conf_default_group_info; } void _config_current_group_pop(void) { struct dns_conf_group_info *group_info = NULL; group_info = list_last_entry(&dns_conf_group_info_list, struct dns_conf_group_info, list); if (group_info == NULL) { return; } if (group_info == dns_conf_default_group_info) { dns_conf_current_group_info = dns_conf_default_group_info; return; } list_del(&group_info->list); free(group_info); group_info = list_last_entry(&dns_conf_group_info_list, struct dns_conf_group_info, list); if (group_info == NULL) { dns_conf_current_group_info = NULL; return; } dns_conf_current_group_info = group_info; } static int _config_domain_rule_iter_copy(void *data, const unsigned char *key, uint32_t key_len, void *value) { art_tree *dest_tree = data; struct dns_domain_rule *old_domain_rule = NULL; struct dns_domain_rule *new_domain_rule = NULL; old_domain_rule = (struct dns_domain_rule *)value; /* Allocate new domain rule with same capacity as original */ size_t size = sizeof(struct dns_domain_rule) + old_domain_rule->capacity * sizeof(struct dns_rule *); new_domain_rule = zalloc(1, size); if (new_domain_rule == NULL) { return -1; } new_domain_rule->capacity = old_domain_rule->capacity; /* Copy rules using actual capacity, not DOMAIN_RULE_MAX */ for (int i = 0; i < old_domain_rule->capacity; i++) { if (old_domain_rule->rules[i]) { _dns_rule_get(old_domain_rule->rules[i]); new_domain_rule->rules[i] = old_domain_rule->rules[i]; } } old_domain_rule = art_insert(dest_tree, key, key_len, new_domain_rule); if (old_domain_rule) { _config_domain_rule_free(old_domain_rule); } return 0; } static int _config_rule_group_setup_value(struct dns_conf_group_info *group_info) { struct dns_conf_group *group_rule = group_info->rule; int soa_talbe_size = MAX_QTYPE_NUM / 8 + 1; uint8_t *soa_table = NULL; struct dns_conf_group *parent_group = _config_current_rule_group(); if (group_info->inherit_group != NULL) { if (strncmp(group_info->inherit_group, "none", sizeof("none")) == 0) { parent_group = NULL; } else if (strncmp(group_info->inherit_group, "parent", sizeof("parent")) == 0) { parent_group = _config_current_rule_group(); } else if (strncmp(group_info->inherit_group, "default", sizeof("default")) == 0) { parent_group = dns_server_get_default_rule_group(); } else { parent_group = _config_rule_group_get(group_info->inherit_group); if (parent_group == NULL) { tlog(TLOG_WARN, "inherit group %s not exist.", group_info->inherit_group); return -1; } } } soa_table = zalloc(1, soa_talbe_size); if (soa_table == NULL) { tlog(TLOG_WARN, "malloc qtype soa table failed."); return -1; } group_rule->soa_table = soa_table; if (parent_group != NULL) { /* copy parent group data. */ memcpy(&group_rule->copy_data_section_begin, &parent_group->copy_data_section_begin, offsetof(struct dns_conf_group, copy_data_section_end) - offsetof(struct dns_conf_group, copy_data_section_begin)); memcpy(group_rule->soa_table, parent_group->soa_table, soa_talbe_size); art_iter(&parent_group->domain_rule.tree, _config_domain_rule_iter_copy, &group_rule->domain_rule.tree); return 0; } memcpy(&group_rule->check_orders, &dns_conf.default_check_orders, sizeof(group_rule->check_orders)); group_rule->dualstack_ip_selection = 1; group_rule->dns_dualstack_ip_selection_threshold = 10; group_rule->dns_rr_ttl_min = 600; group_rule->dns_serve_expired = 1; if (group_rule->dns_prefetch == 1) { group_rule->dns_serve_expired_ttl = 24 * 3600 * 7; } else { group_rule->dns_serve_expired_ttl = 24 * 3600; } group_rule->dns_serve_expired_reply_ttl = 3; group_rule->dns_max_reply_ip_num = DNS_MAX_REPLY_IP_NUM; group_rule->dns_response_mode = dns_conf.default_response_mode; return 0; } int _config_current_group_push(const char *group_name, const char *inherit_group_name) { struct dns_conf_group_info *group_info = NULL; struct dns_conf_group *group_rule = NULL; group_info = zalloc(1, sizeof(*group_info)); if (group_info == NULL) { goto errout; } if (dns_conf_default_group_info != NULL) { group_name = _dns_conf_get_group_name(group_name); if (group_name == NULL) { goto errout; } } if (inherit_group_name == NULL && _config_current_rule_group() != NULL) { inherit_group_name = _config_current_rule_group()->group_name; } memset(group_info, 0, sizeof(*group_info)); group_info->inherit_group = inherit_group_name; INIT_LIST_HEAD(&group_info->list); list_add_tail(&group_info->list, &dns_conf_group_info_list); group_rule = _config_rule_group_get(group_name); if (group_rule == NULL) { group_rule = _config_rule_group_new(group_name); if (group_rule == NULL) { goto errout; } } group_info->group_name = group_name; group_info->rule = group_rule; _config_rule_group_setup_value(group_info); dns_conf_current_group_info = group_info; if (dns_conf_default_group_info == NULL) { dns_conf_default_group_info = group_info; } return 0; errout: if (group_info) { free(group_info); } return -1; } int _config_current_group_push_default(void) { return _config_current_group_push(NULL, NULL); } int _config_current_group_pop_to(struct dns_conf_group_info *group_info) { while (dns_conf_current_group_info != NULL && dns_conf_current_group_info != group_info) { _config_current_group_pop(); } return 0; } int _config_current_group_pop_to_default(void) { return _config_current_group_pop_to(dns_conf_default_group_info); } int _config_current_group_pop_all(void) { while (dns_conf_current_group_info != NULL && dns_conf_current_group_info != dns_conf_default_group_info) { _config_current_group_pop(); } if (dns_conf_default_group_info == NULL) { return 0; } list_del(&dns_conf_default_group_info->list); free(dns_conf_default_group_info); dns_conf_default_group_info = NULL; dns_conf_current_group_info = NULL; return 0; } struct dns_conf_group *_config_rule_group_get(const char *group_name) { uint32_t key = 0; struct dns_conf_group *rule_group = NULL; if (group_name == NULL) { group_name = ""; } key = hash_string(group_name); hash_for_each_possible(dns_conf_rule.group, rule_group, node, key) { if (strncmp(rule_group->group_name, group_name, DNS_GROUP_NAME_LEN) == 0) { return rule_group; } } return NULL; } struct dns_conf_group *dns_server_get_rule_group(const char *group_name) { if (dns_conf_rule.group_num <= 1) { return dns_conf_rule.default_conf; } struct dns_conf_group *rule_group = _config_rule_group_get(group_name); if (rule_group) { return rule_group; } return dns_conf_rule.default_conf; } struct dns_conf_group *dns_server_get_default_rule_group(void) { return dns_conf_rule.default_conf; } struct dns_conf_group *_config_rule_group_new(const char *group_name) { struct dns_conf_group *rule_group = NULL; uint32_t key = 0; if (group_name == NULL) { return NULL; } rule_group = zalloc(1, sizeof(*rule_group)); if (rule_group == NULL) { return NULL; } rule_group->group_name = group_name; INIT_HLIST_NODE(&rule_group->node); art_tree_init(&rule_group->domain_rule.tree); rule_group->address_rule.ipv4 = New_Radix(); rule_group->address_rule.ipv6 = New_Radix(); key = hash_string(group_name); hash_add(dns_conf_rule.group, &rule_group->node, key); dns_conf_rule.group_num++; return rule_group; } static void _config_rule_group_remove(struct dns_conf_group *rule_group) { hlist_del_init(&rule_group->node); art_iter(&rule_group->domain_rule.tree, _config_domain_iter_free, NULL); art_tree_destroy(&rule_group->domain_rule.tree); Destroy_Radix(rule_group->address_rule.ipv4, _config_ip_iter_free, NULL); Destroy_Radix(rule_group->address_rule.ipv6, _config_ip_iter_free, NULL); free(rule_group->soa_table); dns_conf_rule.group_num--; free(rule_group); } void _config_rule_group_destroy(void) { struct dns_conf_group *group; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(dns_conf_rule.group, i, tmp, group, node) { _config_rule_group_remove(group); } dns_conf_rule.default_conf = NULL; } void _dns_conf_group_post(void) { struct dns_conf_group *group; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(dns_conf_rule.group, i, tmp, group, node) { if ((group->dns_rr_ttl_min > group->dns_rr_ttl_max) && group->dns_rr_ttl_max > 0) { group->dns_rr_ttl_min = group->dns_rr_ttl_max; } if ((group->dns_rr_ttl_max < group->dns_rr_ttl_min) && group->dns_rr_ttl_max > 0) { group->dns_rr_ttl_max = group->dns_rr_ttl_min; } if (group->dns_serve_expired == 1 && group->dns_serve_expired_ttl == 0) { group->dns_serve_expired_ttl = DNS_MAX_SERVE_EXPIRED_TIME; } } } ================================================ FILE: src/dns_conf/dns_conf_group.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_CONF_GROUP_H_ #define _DNS_CONF_CONF_GROUP_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ struct dns_conf_group_info { struct list_head list; const char *group_name; const char *inherit_group; struct dns_conf_group *rule; }; extern struct dns_conf_rule dns_conf_rule; // NOLINTNEXTLINE(bugprone-casting-through-void): offsetof result stored as void* for generic interface #define group_member(m) ((void *)offsetof(struct dns_conf_group, m)) int _dns_conf_group_int(int value, int *data); int _dns_conf_group_int_base(int value, int *data); int _dns_conf_group_string(const char *value, char *data); int _dns_conf_group_yesno(int value, int *data); int _dns_conf_group_size(size_t value, size_t *data); int _dns_conf_group_ssize(ssize_t value, ssize_t *data); int _dns_conf_group_enum(int value, int *data); int _config_rule_group_init(void); void _config_rule_group_destroy(void); struct dns_conf_group *_config_rule_group_new(const char *group_name); struct dns_conf_group *_config_current_rule_group(void); struct dns_conf_group_info *_config_current_group(void); struct dns_conf_group_info *_config_default_group(void); void _config_set_current_group(struct dns_conf_group_info *group_info); void _config_current_group_pop(void); int _config_current_group_push(const char *group_name, const char *inherit_group_name); int _config_current_group_push_default(void); int _config_current_group_pop_to_default(void); int _config_current_group_pop_to(struct dns_conf_group_info *group_info); int _config_current_group_pop_all(void); void _dns_conf_group_post(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/domain_rule.c ================================================ /************************************************************************* * * 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 . */ #include "domain_rule.h" #include "address.h" #include "cname.h" #include "dns_conf_group.h" #include "https_record.h" #include "ipset.h" #include "nameserver.h" #include "nftset.h" #include "server_group.h" #include "set_file.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include "speed_check_mode.h" #include static inline uint8_t _get_required_capacity(enum domain_rule type, uint8_t current_capacity) { uint8_t required = type + 1; /* Ensure type is within valid range */ if (type >= DOMAIN_RULE_MAX || type < 0) { return 0; } if (current_capacity == 0) { return required; } /* Expand by 2 slots at a time, but cap at DOMAIN_RULE_MAX */ uint8_t new_capacity = ((required - current_capacity + 1) / 2) * 2 + current_capacity; if (new_capacity > DOMAIN_RULE_MAX) { new_capacity = DOMAIN_RULE_MAX; } return new_capacity; } static struct dns_domain_rule *_alloc_domain_rule(uint8_t capacity) { size_t size = sizeof(struct dns_domain_rule) + capacity * sizeof(struct dns_rule *); struct dns_domain_rule *rule = malloc(size); if (rule == NULL) { return NULL; } memset(rule, 0, size); rule->capacity = capacity; return rule; } /* * Ensure the domain rule has enough capacity for the given rule type * Reallocates if necessary, preserving existing rules */ static struct dns_domain_rule *_ensure_domain_rule_capacity(struct dns_domain_rule *domain_rule, enum domain_rule type) { if (type >= DOMAIN_RULE_MAX || type < 0) { tlog(TLOG_ERROR, "Invalid domain rule type %d", type); return NULL; } if (domain_rule == NULL) { uint8_t capacity = _get_required_capacity(type, 0); return _alloc_domain_rule(capacity); } if (type < domain_rule->capacity) { return domain_rule; } uint8_t new_capacity = _get_required_capacity(type, domain_rule->capacity); if (new_capacity == 0) { return NULL; } if (new_capacity <= domain_rule->capacity) { return domain_rule; } size_t new_size = sizeof(struct dns_domain_rule) + new_capacity * sizeof(struct dns_rule *); struct dns_domain_rule *new_rule = realloc(domain_rule, new_size); if (new_rule == NULL) { return NULL; } uint8_t old_capacity = new_rule->capacity; memset((void *)(new_rule->rules + old_capacity), 0, (new_capacity - old_capacity) * sizeof(struct dns_rule *)); new_rule->capacity = new_capacity; return new_rule; } struct dns_rule_info { int size; int (*get_size)(struct dns_rule *rule); void (*clone)(struct dns_rule *new_rule, struct dns_rule *old_rule); }; static int _rule_address_ipv4_get_size(struct dns_rule *rule) { return sizeof(struct dns_rule_address_IPV4) + ((struct dns_rule_address_IPV4 *)rule)->addr_num * DNS_RR_A_LEN; } static int _rule_address_ipv6_get_size(struct dns_rule *rule) { return sizeof(struct dns_rule_address_IPV6) + ((struct dns_rule_address_IPV6 *)rule)->addr_num * DNS_RR_AAAA_LEN; } static void _rule_https_clone(struct dns_rule *new_rule, struct dns_rule *old_rule) { struct dns_https_record_rule *new_https = (struct dns_https_record_rule *)new_rule; struct dns_https_record_rule *old_https = (struct dns_https_record_rule *)old_rule; struct dns_https_record *record; INIT_LIST_HEAD(&new_https->record_list); if (old_https->record_list.next != NULL && old_https->record_list.prev != NULL) { list_for_each_entry(record, &old_https->record_list, list) { struct dns_https_record *new_record = malloc(sizeof(struct dns_https_record)); if (new_record) { memcpy(new_record, record, sizeof(struct dns_https_record)); list_add_tail(&new_record->list, &new_https->record_list); } } } } static void _rule_srv_clone(struct dns_rule *new_rule, struct dns_rule *old_rule) { struct dns_srv_record_rule *new_srv = (struct dns_srv_record_rule *)new_rule; struct dns_srv_record_rule *old_srv = (struct dns_srv_record_rule *)old_rule; struct dns_srv_record *record; INIT_LIST_HEAD(&new_srv->record_list); if (old_srv->record_list.next != NULL && old_srv->record_list.prev != NULL) { list_for_each_entry(record, &old_srv->record_list, list) { struct dns_srv_record *new_record = malloc(sizeof(struct dns_srv_record)); if (new_record) { memcpy(new_record, record, sizeof(struct dns_srv_record)); list_add_tail(&new_record->list, &new_srv->record_list); } } } } static struct dns_rule_info dns_rule_info_table[DOMAIN_RULE_MAX] = { [DOMAIN_RULE_FLAGS] = {sizeof(struct dns_rule_flags), NULL, NULL}, [DOMAIN_RULE_ADDRESS_IPV4] = {sizeof(struct dns_rule_address_IPV4), _rule_address_ipv4_get_size, NULL}, [DOMAIN_RULE_ADDRESS_IPV6] = {sizeof(struct dns_rule_address_IPV6), _rule_address_ipv6_get_size, NULL}, [DOMAIN_RULE_NAMESERVER] = {sizeof(struct dns_nameserver_rule), NULL, NULL}, [DOMAIN_RULE_CHECKSPEED] = {sizeof(struct dns_domain_check_orders), NULL, NULL}, [DOMAIN_RULE_IPSET] = {sizeof(struct dns_ipset_rule), NULL, NULL}, [DOMAIN_RULE_NFTSET_IP] = {sizeof(struct dns_nftset_rule), NULL, NULL}, [DOMAIN_RULE_IPSET_IPV4] = {sizeof(struct dns_ipset_rule), NULL, NULL}, [DOMAIN_RULE_GROUP] = {sizeof(struct dns_group_rule), NULL, NULL}, [DOMAIN_RULE_NFTSET_IP6] = {sizeof(struct dns_nftset_rule), NULL, NULL}, [DOMAIN_RULE_IPSET_IPV6] = {sizeof(struct dns_ipset_rule), NULL, NULL}, [DOMAIN_RULE_HTTPS] = {sizeof(struct dns_https_record_rule), NULL, _rule_https_clone}, [DOMAIN_RULE_SRV] = {sizeof(struct dns_srv_record_rule), NULL, _rule_srv_clone}, [DOMAIN_RULE_RESPONSE_MODE] = {sizeof(struct dns_response_mode_rule), NULL, NULL}, [DOMAIN_RULE_CNAME] = {sizeof(struct dns_cname_rule), NULL, NULL}, [DOMAIN_RULE_TTL] = {sizeof(struct dns_ttl_rule), NULL, NULL}, }; void *_new_dns_rule_ext(enum domain_rule domain_rule, int ext_size) { struct dns_rule *rule; int size = 0; if (domain_rule >= DOMAIN_RULE_MAX) { return NULL; } size = dns_rule_info_table[domain_rule].size + ext_size; if (size <= 0) { return NULL; } rule = zalloc(1, size); if (!rule) { return NULL; } rule->rule = domain_rule; atomic_set(&rule->refcnt, 1); return rule; } void *_new_dns_rule(enum domain_rule domain_rule) { return _new_dns_rule_ext(domain_rule, 0); } static struct dns_rule *_dns_rule_clone(struct dns_rule *rule) { int size = 0; struct dns_rule *new_rule; if (rule == NULL || rule->rule >= DOMAIN_RULE_MAX) { return NULL; } if (dns_rule_info_table[rule->rule].get_size) { size = dns_rule_info_table[rule->rule].get_size(rule); } else { size = dns_rule_info_table[rule->rule].size; } if (size <= 0) { return NULL; } new_rule = zalloc(1, size); if (!new_rule) { return NULL; } memcpy(new_rule, rule, size); atomic_set(&new_rule->refcnt, 1); if (dns_rule_info_table[rule->rule].clone) { dns_rule_info_table[rule->rule].clone(new_rule, rule); } return new_rule; } void _dns_rule_get(struct dns_rule *rule) { atomic_inc(&rule->refcnt); } static void _dns_rule_free(struct dns_rule *rule) { if (rule->rule == DOMAIN_RULE_HTTPS) { struct dns_https_record_rule *https_rule = (struct dns_https_record_rule *)rule; struct dns_https_record *record, *tmp; if (https_rule->record_list.next != NULL && https_rule->record_list.prev != NULL) { list_for_each_entry_safe(record, tmp, &https_rule->record_list, list) { list_del(&record->list); free(record); } } } else if (rule->rule == DOMAIN_RULE_SRV) { struct dns_srv_record_rule *srv_rule = (struct dns_srv_record_rule *)rule; struct dns_srv_record *record, *tmp; if (srv_rule->record_list.next != NULL && srv_rule->record_list.prev != NULL) { list_for_each_entry_safe(record, tmp, &srv_rule->record_list, list) { list_del(&record->list); free(record); } } } free(rule); } void _dns_rule_put(struct dns_rule *rule) { if (atomic_dec_and_test(&rule->refcnt)) { _dns_rule_free(rule); } } static struct dns_domain_set_name_list *_config_get_domain_set_name_list(const char *name) { uint32_t key = 0; struct dns_domain_set_name_list *set_name_list = NULL; key = hash_string(name); hash_for_each_possible(dns_domain_set_name_table.names, set_name_list, node, key) { if (strcmp(set_name_list->name, name) == 0) { return set_name_list; } } return NULL; } static int _config_domain_rule_set_each(const char *domain_set, set_rule_add_func callback, void *priv) { struct dns_domain_set_name_list *set_name_list = NULL; struct dns_domain_set_name *set_name_item = NULL; set_name_list = _config_get_domain_set_name_list(domain_set); if (set_name_list == NULL) { tlog(TLOG_WARN, "domain set %s not found.", domain_set); return -1; } list_for_each_entry(set_name_item, &set_name_list->set_name_list, list) { switch (set_name_item->type) { case DNS_DOMAIN_SET_LIST: if (_config_set_rule_each_from_list(set_name_item->file, callback, priv) != 0) { return -1; } break; case DNS_DOMAIN_SET_GEOSITE: break; default: tlog(TLOG_WARN, "domain set %s type %d not support.", set_name_list->name, set_name_item->type); break; } } return 0; } static int _config_domain_rule_add_callback(const char *domain, void *priv) { struct dns_set_rule_add_callback_args *args = (struct dns_set_rule_add_callback_args *)priv; return _config_domain_rule_add(domain, args->type, args->rule); } static int _config_setup_domain_key(const char *domain, char *domain_key, int domain_key_max_len, int *domain_key_len, int *root_rule_only, int *sub_rule_only) { int tmp_root_rule_only = 0; int tmp_sub_rule_only = 0; int domain_len = 0; int len = strlen(domain); domain_len = len; if (!domain_key || !domain_key_len || domain_key_max_len <= 0 || len + 3 > domain_key_max_len) { tlog(TLOG_ERROR, "invalid parameters or domain too long: %s (max %d)", domain, domain_key_max_len - 3); return -1; } while (len > 0 && domain[len - 1] == '.') { len--; } reverse_string(domain_key + 1, domain, len, 1); if (domain[0] == '*' && domain_len > 1) { /* prefix wildcard */ len--; if (domain[1] == '.') { tmp_sub_rule_only = 1; } else if ((domain[1] == '-') && (domain[2] == '.')) { len--; tmp_sub_rule_only = 1; tmp_root_rule_only = 1; } } else if (domain[0] == '-' && domain_len > 1) { /* root match only */ len--; if (domain[1] == '.') { tmp_root_rule_only = 1; } } else if (len > 0) { /* suffix match */ if (len + 2 < domain_key_max_len) { domain_key[len + 1] = '.'; len++; } } /* add dot to the front when sub rule only */ domain_key[0] = '.'; if (tmp_sub_rule_only == 1 && tmp_root_rule_only == 0) { domain_key[len + 1] = '\0'; } else if (tmp_root_rule_only == 1 && tmp_sub_rule_only == 0) { if (domain_key[len] == '.') { len--; } domain_key[len + 1] = '\0'; } else { domain_key[len + 1] = '\0'; } *domain_key_len = len + 1; if (root_rule_only) { *root_rule_only = tmp_root_rule_only; } if (sub_rule_only) { *sub_rule_only = tmp_sub_rule_only; } return 0; } static __attribute__((unused)) struct dns_domain_rule *_config_domain_rule_get(const char *domain) { char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0}; int len = 0; if (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, NULL, NULL) != 0) { return NULL; } return art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); } int _config_domain_rule_free(struct dns_domain_rule *domain_rule) { int i = 0; if (domain_rule == NULL) { return 0; } /* Iterate only through allocated capacity, not DOMAIN_RULE_MAX */ for (i = 0; i < domain_rule->capacity; i++) { if (domain_rule->rules[i] == NULL) { continue; } _dns_rule_put(domain_rule->rules[i]); domain_rule->rules[i] = NULL; } free(domain_rule); return 0; } int _config_domain_iter_free(void *data, const unsigned char *key, uint32_t key_len, void *value) { struct dns_domain_rule *domain_rule = value; return _config_domain_rule_free(domain_rule); } static int _config_domain_rule_delete_callback(const char *domain, void *priv) { return _config_domain_rule_delete(domain); } int _config_domain_rule_delete(const char *domain) { char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0}; int len = 0; if (strncmp(domain, "domain-set:", sizeof("domain-set:") - 1) == 0) { return _config_domain_rule_set_each(domain + sizeof("domain-set:") - 1, _config_domain_rule_delete_callback, NULL); } /* Reverse string, for suffix match */ if (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, NULL, NULL) != 0) { goto errout; } /* delete existing rules */ void *rule = art_delete(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); if (rule) { _config_domain_rule_free(rule); } return 0; errout: tlog(TLOG_ERROR, "delete domain %s rule failed", domain); return -1; } static int _config_domain_rule_flag_callback(const char *domain, void *priv) { struct dns_set_rule_flags_callback_args *args = (struct dns_set_rule_flags_callback_args *)priv; return _config_domain_rule_flag_set(domain, args->flags, args->is_clear_flag); } int _config_domain_rule_flag_set(const char *domain, unsigned int flag, unsigned int is_clear) { struct dns_domain_rule *domain_rule = NULL; struct dns_domain_rule *old_domain_rule = NULL; struct dns_domain_rule *add_domain_rule = NULL; struct dns_rule_flags *rule_flags = NULL; char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0}; int len = 0; int sub_rule_only = 0; int root_rule_only = 0; if (strncmp(domain, "domain-set:", sizeof("domain-set:") - 1) == 0) { struct dns_set_rule_flags_callback_args args; args.flags = flag; args.is_clear_flag = is_clear; return _config_domain_rule_set_each(domain + sizeof("domain-set:") - 1, _config_domain_rule_flag_callback, &args); } if (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, &root_rule_only, &sub_rule_only) != 0) { goto errout; } /* Get existing domain rule */ domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); if (domain_rule == NULL) { /* Allocate new domain rule with minimum capacity for flags */ domain_rule = _alloc_domain_rule(_get_required_capacity(DOMAIN_RULE_FLAGS, 0)); if (domain_rule == NULL) { goto errout; } add_domain_rule = domain_rule; } /* add new rule to domain */ if (domain_rule->rules[DOMAIN_RULE_FLAGS] == NULL) { rule_flags = _new_dns_rule(DOMAIN_RULE_FLAGS); rule_flags->flags = 0; domain_rule->rules[DOMAIN_RULE_FLAGS] = (struct dns_rule *)rule_flags; } rule_flags = (struct dns_rule_flags *)domain_rule->rules[DOMAIN_RULE_FLAGS]; if (atomic_read(&rule_flags->head.refcnt) > 1 && (rule_flags->head.sub_only != sub_rule_only || rule_flags->head.root_only != root_rule_only)) { struct dns_rule_flags *new_flags = (struct dns_rule_flags *)_dns_rule_clone(&rule_flags->head); if (new_flags) { _dns_rule_put(&rule_flags->head); domain_rule->rules[DOMAIN_RULE_FLAGS] = (struct dns_rule *)new_flags; rule_flags = new_flags; } } rule_flags->head.sub_only = sub_rule_only; rule_flags->head.root_only = root_rule_only; if (is_clear == false) { rule_flags->flags |= flag; } else { rule_flags->flags &= ~flag; } rule_flags->is_flag_set |= flag; /* update domain rule */ if (add_domain_rule) { old_domain_rule = art_insert(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len, add_domain_rule); if (old_domain_rule) { _config_domain_rule_free(old_domain_rule); } } return 0; errout: if (add_domain_rule) { free(add_domain_rule); } tlog(TLOG_ERROR, "add domain %s rule failed", domain); return 0; } int _config_domain_rule_remove(const char *domain, enum domain_rule type) { char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0}; int len = 0; int sub_rule_only = 0; int root_rule_only = 0; if (type < 0 || type >= DOMAIN_RULE_MAX) { tlog(TLOG_ERROR, "invalid domain rule type %d", type); return -1; } if (strncmp(domain, "domain-set:", sizeof("domain-set:") - 1) == 0) { return _config_domain_rule_set_each(domain + sizeof("domain-set:") - 1, _config_domain_rule_delete_callback, NULL); } if (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, &root_rule_only, &sub_rule_only) != 0) { tlog(TLOG_ERROR, "setup domain key failed for %s", domain); return -1; } struct dns_domain_rule *domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); if (domain_rule == NULL) { tlog(TLOG_ERROR, "domain %s not found", domain); return -1; } if (domain_rule->rules[type] == NULL) { return 0; } _dns_rule_put(domain_rule->rules[type]); domain_rule->rules[type] = NULL; return 0; } int _config_domain_rule_add(const char *domain, enum domain_rule type, void *rule) { struct dns_domain_rule *domain_rule = NULL; struct dns_domain_rule *old_domain_rule = NULL; struct dns_domain_rule *add_domain_rule = NULL; char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0}; int len = 0; int sub_rule_only = 0; int root_rule_only = 0; if (strncmp(domain, "domain-set:", sizeof("domain-set:") - 1) == 0) { struct dns_set_rule_add_callback_args args; args.type = type; args.rule = rule; return _config_domain_rule_set_each(domain + sizeof("domain-set:") - 1, _config_domain_rule_add_callback, &args); } /* Reverse string, for suffix match */ if (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, &root_rule_only, &sub_rule_only) != 0) { goto errout; } if (type >= DOMAIN_RULE_MAX) { goto errout; } /* Get existing domain rule */ domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); /* Track if this is a new allocation (before capacity expansion) */ int was_new_allocation = (domain_rule == NULL); struct dns_domain_rule *old_ptr = domain_rule; /* Ensure capacity for the new rule type */ domain_rule = _ensure_domain_rule_capacity(domain_rule, type); if (domain_rule == NULL) { tlog(TLOG_ERROR, "failed to allocate capacity for domain %s rule type %d", domain, type); goto errout; } /* Set add_domain_rule if this was a new allocation or if realloc moved the memory */ if (was_new_allocation) { add_domain_rule = domain_rule; } else if (domain_rule != old_ptr) { /* Memory was moved by realloc, need to update ART tree * Note: old_ptr is already freed by realloc, so we don't free it again */ old_domain_rule = art_insert(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len, domain_rule); /* old_domain_rule == old_ptr, already freed by realloc, don't free again */ add_domain_rule = NULL; } /* add new rule to domain */ struct dns_rule *prule = (struct dns_rule *)rule; int is_cloned = 0; if (atomic_read(&prule->refcnt) > 1 && (prule->sub_only != sub_rule_only || prule->root_only != root_rule_only)) { struct dns_rule *new_rule = _dns_rule_clone(prule); if (new_rule) { prule = new_rule; rule = (void *)new_rule; is_cloned = 1; } } if (domain_rule->rules[type]) { _dns_rule_put(domain_rule->rules[type]); domain_rule->rules[type] = NULL; } domain_rule->rules[type] = prule; prule->sub_only = sub_rule_only; prule->root_only = root_rule_only; if (!is_cloned) { _dns_rule_get(prule); } /* update domain rule - only for new allocations */ if (add_domain_rule) { old_domain_rule = art_insert(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len, add_domain_rule); if (old_domain_rule) { _config_domain_rule_free(old_domain_rule); } } return 0; errout: if (add_domain_rule) { free(add_domain_rule); } tlog(TLOG_ERROR, "add domain %s rule failed", domain); return -1; } static int _conf_domain_rule_rr_ttl(const char *domain, int ttl, int ttl_min, int ttl_max) { struct dns_ttl_rule *rr_ttl = NULL; if (ttl < 0 || ttl_min < 0 || ttl_max < 0) { tlog(TLOG_ERROR, "invalid ttl value."); goto errout; } rr_ttl = _new_dns_rule(DOMAIN_RULE_TTL); if (rr_ttl == NULL) { goto errout; } rr_ttl->ttl = ttl; rr_ttl->ttl_min = ttl_min; rr_ttl->ttl_max = ttl_max; if (_config_domain_rule_add(domain, DOMAIN_RULE_TTL, rr_ttl) != 0) { goto errout; } _dns_rule_put(&rr_ttl->head); return 0; errout: if (rr_ttl != NULL) { _dns_rule_put(&rr_ttl->head); } return -1; } static int _conf_domain_rule_no_serve_expired(const char *domain) { return _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_SERVE_EXPIRED, 0); } static int _conf_domain_rule_delete(const char *domain) { return _config_domain_rule_delete(domain); } static int _conf_domain_rule_no_cache(const char *domain) { return _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_CACHE, 0); } static int _conf_domain_rule_enable_cache(const char *domain) { return _config_domain_rule_flag_set(domain, DOMAIN_FLAG_ENABLE_CACHE, 0); } static int _conf_domain_rule_no_ipalias(const char *domain) { return _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_IPALIAS, 0); } static int _conf_domain_rule_no_ignore_ip(const char *domain) { return _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_IGNORE_IP, 0); } int _conf_domain_rule_response_mode(char *domain, const char *mode) { enum response_mode_type response_mode_type = DNS_RESPONSE_MODE_FIRST_PING_IP; struct dns_response_mode_rule *response_mode = NULL; for (int i = 0; response_mode_list()[i].name != NULL; i++) { if (strcmp(mode, response_mode_list()[i].name) == 0) { response_mode_type = response_mode_list()[i].id; break; } } response_mode = _new_dns_rule(DOMAIN_RULE_RESPONSE_MODE); if (response_mode == NULL) { goto errout; } response_mode->mode = response_mode_type; if (_config_domain_rule_add(domain, DOMAIN_RULE_RESPONSE_MODE, response_mode) != 0) { goto errout; } _dns_rule_put(&response_mode->head); return 0; errout: if (response_mode) { _dns_rule_put(&response_mode->head); } return 0; } int _conf_domain_rule_speed_check(char *domain, const char *mode) { struct dns_domain_check_orders *check_orders = NULL; check_orders = _new_dns_rule(DOMAIN_RULE_CHECKSPEED); if (check_orders == NULL) { goto errout; } if (_config_speed_check_mode_parser(check_orders, mode) != 0) { goto errout; } if (_config_domain_rule_add(domain, DOMAIN_RULE_CHECKSPEED, check_orders) != 0) { goto errout; } _dns_rule_put(&check_orders->head); return 0; errout: if (check_orders) { _dns_rule_put(&check_orders->head); } return 0; } int _conf_domain_rule_group(const char *domain, const char *group_name) { struct dns_group_rule *group_rule = NULL; const char *group = NULL; if (strncmp(group_name, "-", sizeof("-")) != 0) { group = _dns_conf_get_group_name(group_name); if (group == NULL) { goto errout; } group_rule = _new_dns_rule(DOMAIN_RULE_GROUP); if (group_rule == NULL) { goto errout; } group_rule->group_name = group; } else { /* ignore this domain */ if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_GROUP_IGNORE, 0) != 0) { goto errout; } return 0; } if (_config_domain_rule_add(domain, DOMAIN_RULE_GROUP, group_rule) != 0) { goto errout; } _dns_rule_put(&group_rule->head); return 0; errout: if (group_rule) { _dns_rule_put(&group_rule->head); } tlog(TLOG_ERROR, "add group %s, %s failed", domain, group_name); return 0; } static int _conf_domain_rule_dualstack_selection(char *domain, const char *yesno) { if (strncmp(yesno, "yes", sizeof("yes")) == 0 || strncmp(yesno, "Yes", sizeof("Yes")) == 0) { if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_DUALSTACK_SELECT, 0) != 0) { goto errout; } } else { /* ignore this domain */ if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_DUALSTACK_SELECT, 1) != 0) { goto errout; } } return 0; errout: tlog(TLOG_ERROR, "set dualstack for %s failed. ", domain); return 1; } int _config_domain_rules(void *data, int argc, char *argv[]) { int opt = 0; int optind_last = 0; char domain[DNS_MAX_CONF_CNAME_LEN]; char *value = argv[1]; int rr_ttl = 0; int rr_ttl_min = 0; int rr_ttl_max = 0; const char *group = NULL; char group_name[DNS_MAX_CONF_CNAME_LEN]; /* clang-format off */ static struct option long_options[] = { {"speed-check-mode", required_argument, NULL, 'c'}, {"response-mode", required_argument, NULL, 'r'}, {"address", required_argument, NULL, 'a'}, {"https-record", required_argument, NULL, 'h'}, {"ipset", required_argument, NULL, 'p'}, {"nftset", required_argument, NULL, 't'}, {"nameserver", required_argument, NULL, 'n'}, {"group", required_argument, NULL, 'g'}, {"dualstack-ip-selection", required_argument, NULL, 'd'}, {"cname", required_argument, NULL, 'A'}, {"rr-ttl", required_argument, NULL, 251}, {"rr-ttl-min", required_argument, NULL, 252}, {"rr-ttl-max", required_argument, NULL, 253}, {"no-serve-expired", no_argument, NULL, 254}, {"delete", no_argument, NULL, 255}, {"no-cache", no_argument, NULL, 256}, {"no-ip-alias", no_argument, NULL, 257}, {"enable-cache", no_argument, NULL, 258}, {"no-ignore-ip", no_argument, NULL, 259}, {NULL, no_argument, NULL, 0} }; /* clang-format on */ if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) { goto errout; } /* check domain set exists. */ if (strncmp(domain, "domain-set:", sizeof("domain-set:") - 1) == 0) { const char *set_name = domain + sizeof("domain-set:") - 1; struct dns_domain_set_name_list *name = _config_get_domain_set_name_list(set_name); if (name == NULL) { tlog(TLOG_ERROR, "domain set '%s' not found.", set_name); goto errout; } } for (int i = 2; i < argc - 1; i++) { if (strncmp(argv[i], "-g", sizeof("-g")) == 0 || strncmp(argv[i], "--group", sizeof("--group")) == 0 || strncmp(argv[i], "-group", sizeof("-group")) == 0) { safe_strncpy(group_name, argv[i + 1], DNS_MAX_CONF_CNAME_LEN); group = group_name; break; } } if (group != NULL) { _config_current_group_push(group, NULL); } /* process extra options */ optind = 1; optind_last = 1; while (1) { opt = getopt_long_only(argc, argv, "c:a:p:t:n:d:A:r:g:h:", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'c': { const char *check_mode = optarg; if (check_mode == NULL) { goto errout; } if (_conf_domain_rule_speed_check(domain, check_mode) != 0) { tlog(TLOG_ERROR, "add check-speed-rule rule failed."); goto errout; } break; } case 'r': { const char *response_mode = optarg; if (response_mode == NULL) { goto errout; } if (_conf_domain_rule_response_mode(domain, response_mode) != 0) { tlog(TLOG_ERROR, "add response-mode rule failed."); goto errout; } break; } case 'a': { const char *address = optarg; if (address == NULL) { goto errout; } if (_conf_domain_rule_address(domain, address) != 0) { tlog(TLOG_ERROR, "add address rule failed."); goto errout; } break; } case 'h': { const char *https_record = optarg; if (https_record == NULL) { goto errout; } if (_conf_domain_rule_https_record(domain, https_record) != 0) { tlog(TLOG_ERROR, "add https-record rule failed."); goto errout; } break; } case 'p': { const char *ipsetname = optarg; if (ipsetname == NULL) { goto errout; } if (_conf_domain_rule_ipset(domain, ipsetname) != 0) { tlog(TLOG_ERROR, "add ipset rule failed."); goto errout; } break; } case 'n': { const char *nameserver_group = optarg; if (nameserver_group == NULL) { goto errout; } if (_conf_domain_rule_nameserver(domain, nameserver_group) != 0) { tlog(TLOG_ERROR, "add nameserver rule failed."); goto errout; } break; } case 'A': { const char *cname = optarg; if (_conf_domain_rule_cname(domain, cname) != 0) { tlog(TLOG_ERROR, "add cname rule failed."); goto errout; } break; } case 'd': { const char *yesno = optarg; if (_conf_domain_rule_dualstack_selection(domain, yesno) != 0) { tlog(TLOG_ERROR, "set dualstack selection rule failed."); goto errout; } break; } case 't': { const char *nftsetname = optarg; if (nftsetname == NULL) { goto errout; } if (_conf_domain_rule_nftset(domain, nftsetname) != 0) { tlog(TLOG_ERROR, "add nftset rule failed."); goto errout; } break; } case 'g': { break; } case 251: { rr_ttl = atoi(optarg); break; } case 252: { rr_ttl_min = atoi(optarg); break; } case 253: { rr_ttl_max = atoi(optarg); break; } case 254: { if (_conf_domain_rule_no_serve_expired(domain) != 0) { tlog(TLOG_ERROR, "set no-serve-expired rule failed."); goto errout; } break; } case 255: { if (_conf_domain_rule_delete(domain) != 0) { tlog(TLOG_ERROR, "delete domain rule failed."); goto errout; } return 0; } case 256: { if (_conf_domain_rule_no_cache(domain) != 0) { tlog(TLOG_ERROR, "set no-cache rule failed."); goto errout; } break; } case 257: { if (_conf_domain_rule_no_ipalias(domain) != 0) { tlog(TLOG_ERROR, "set no-ipalias rule failed."); goto errout; } break; } case 258: { if (_conf_domain_rule_enable_cache(domain) != 0) { tlog(TLOG_ERROR, "set enable-cache rule failed."); goto errout; } break; } case 259: { if (_conf_domain_rule_no_ignore_ip(domain) != 0) { tlog(TLOG_ERROR, "set no-ignore-ip rule failed."); goto errout; } break; } default: if (optind > optind_last) { tlog(TLOG_WARN, "unknown domain-rules option: %s at '%s:%d'.", argv[optind - 1], conf_get_conf_file(), conf_get_current_lineno()); } break; } optind_last = optind; } if (rr_ttl > 0 || rr_ttl_min > 0 || rr_ttl_max > 0) { if (_conf_domain_rule_rr_ttl(domain, rr_ttl, rr_ttl_min, rr_ttl_max) != 0) { tlog(TLOG_ERROR, "set rr-ttl rule failed."); goto errout; } } if (group != NULL) { _config_current_group_pop(); } return 0; errout: if (group != NULL) { _config_current_group_pop(); } return -1; } void *dns_conf_get_domain_rule(const char *domain, enum domain_rule type) { struct dns_domain_rule *domain_rule = NULL; char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0}; int len = 0; int sub_rule_only = 0; int root_rule_only = 0; if (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, &root_rule_only, &sub_rule_only) != 0) { return NULL; } domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); if (domain_rule == NULL) { return NULL; } if (type >= DOMAIN_RULE_MAX || type >= domain_rule->capacity) { return NULL; } return domain_rule->rules[type]; } ================================================ FILE: src/dns_conf/domain_rule.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_DOMAIN_RULE_H_ #define _DNS_CONF_DOMAIN_RULE_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_domain_iter_free(void *data, const unsigned char *key, uint32_t key_len, void *value); void *_new_dns_rule_ext(enum domain_rule domain_rule, int ext_size); void *_new_dns_rule(enum domain_rule domain_rule); void _dns_rule_get(struct dns_rule *rule); void _dns_rule_put(struct dns_rule *rule); int _config_domain_rule_add(const char *domain, enum domain_rule type, void *rule); int _config_domain_rule_remove(const char *domain, enum domain_rule type); int _config_domain_rule_flag_set(const char *domain, unsigned int flag, unsigned int is_clear); int _config_domain_rules(void *data, int argc, char *argv[]); int _config_domain_rule_delete(const char *domain); int _conf_domain_rule_group(const char *domain, const char *group_name); void *dns_conf_get_domain_rule(const char *domain, enum domain_rule type); int _config_domain_rule_free(struct dns_domain_rule *domain_rule); int _conf_domain_rule_speed_check(char *domain, const char *mode); int _conf_domain_rule_response_mode(char *domain, const char *mode); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/domain_set.c ================================================ /************************************************************************* * * 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 . */ #include "domain_set.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include #include #include struct dns_domain_set_name_table dns_domain_set_name_table; int _config_domain_set(void *data, int argc, char *argv[]) { int opt = 0; uint32_t key = 0; struct dns_domain_set_name *domain_set = NULL; struct dns_domain_set_name_list *domain_set_name_list = NULL; char set_name[DNS_MAX_CNAME_LEN] = {0}; /* clang-format off */ static struct option long_options[] = { {"name", required_argument, NULL, 'n'}, {"type", required_argument, NULL, 't'}, {"file", required_argument, NULL, 'f'}, {NULL, 0, NULL, 0} }; if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } domain_set = zalloc(1, sizeof(*domain_set)); if (domain_set == NULL) { tlog(TLOG_ERROR, "cannot malloc memory."); goto errout; } INIT_LIST_HEAD(&domain_set->list); optind = 1; while (1) { opt = getopt_long_only(argc, argv, "n:t:f:", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'n': safe_strncpy(set_name, optarg, DNS_MAX_CNAME_LEN); break; case 't': { const char *type = optarg; if (strncmp(type, "list", 5) == 0) { domain_set->type = DNS_DOMAIN_SET_LIST; } else if (strncmp(type, "geosite", 7) == 0) { domain_set->type = DNS_DOMAIN_SET_GEOSITE; } else { tlog(TLOG_ERROR, "invalid domain set type."); goto errout; } break; } case 'f': conf_get_conf_fullpath(optarg, domain_set->file, DNS_MAX_PATH); break; default: break; } } /* clang-format on */ if (set_name[0] == 0 || domain_set->file[0] == 0) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } if (access(domain_set->file, F_OK) != 0) { tlog(TLOG_ERROR, "domain set file %s not readable. %s", domain_set->file, strerror(errno)); goto errout; } key = hash_string(set_name); hash_for_each_possible(dns_domain_set_name_table.names, domain_set_name_list, node, key) { if (strcmp(domain_set_name_list->name, set_name) == 0) { break; } } if (domain_set_name_list == NULL) { domain_set_name_list = zalloc(1, sizeof(*domain_set_name_list)); if (domain_set_name_list == NULL) { tlog(TLOG_ERROR, "cannot malloc memory."); goto errout; } INIT_LIST_HEAD(&domain_set_name_list->set_name_list); safe_strncpy(domain_set_name_list->name, set_name, DNS_MAX_CNAME_LEN); hash_add(dns_domain_set_name_table.names, &domain_set_name_list->node, key); } list_add_tail(&domain_set->list, &domain_set_name_list->set_name_list); return 0; errout: if (domain_set) { free(domain_set); } return -1; } void _config_domain_set_name_table_init(void) { hash_init(dns_domain_set_name_table.names); } void _config_domain_set_name_table_destroy(void) { struct dns_domain_set_name_list *set_name_list = NULL; struct hlist_node *tmp = NULL; struct dns_domain_set_name *set_name = NULL; struct dns_domain_set_name *tmp1 = NULL; unsigned long i = 0; hash_for_each_safe(dns_domain_set_name_table.names, i, tmp, set_name_list, node) { hlist_del_init(&set_name_list->node); list_for_each_entry_safe(set_name, tmp1, &set_name_list->set_name_list, list) { list_del(&set_name->list); free(set_name); } free(set_name_list); } } ================================================ FILE: src/dns_conf/domain_set.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_DOMAIN_SET_H_ #define _DNS_CONF_DOMAIN_SET_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_domain_set(void *data, int argc, char *argv[]); void _config_domain_set_name_table_init(void); void _config_domain_set_name_table_destroy(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/get_domain.c ================================================ /************************************************************************* * * 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 . */ #include "get_domain.h" #include "smartdns/lib/idna.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" int _get_domain(char *value, char *domain, int max_domain_size, char **ptr_after_domain) { char *begin = NULL; char *end = NULL; int len = 0; if (value == NULL || domain == NULL) { goto errout; } /* first field */ begin = strstr(value, "/"); if (begin == NULL) { safe_strncpy(domain, ".", max_domain_size); return 0; } /* second field */ begin++; end = strstr(begin, "/"); if (end == NULL) { goto errout; } /* remove prefix . */ while (*begin == '.') { if (begin + 1 == end) { break; } begin++; } /* Get domain */ len = end - begin; if (len >= max_domain_size) { tlog(TLOG_ERROR, "domain name %s too long", value); goto errout; } size_t domain_len = max_domain_size; if (strncmp(begin, "domain-set:", sizeof("domain-set:") - 1) == 0) { memcpy(domain, begin, len); domain_len = len; } else { domain_len = utf8_to_punycode(begin, len, domain, domain_len); if (domain_len <= 0) { tlog(TLOG_ERROR, "domain name %s invalid", value); goto errout; } } domain[domain_len] = '\0'; if (ptr_after_domain) { *ptr_after_domain = end + 1; } return 0; errout: return -1; } ================================================ FILE: src/dns_conf/get_domain.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_GET_DOMAIN_H_ #define _DNS_CONF_GET_DOMAIN_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _get_domain(char *value, char *domain, int max_domain_size, char **ptr_after_domain); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/group.c ================================================ /************************************************************************* * * 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 . */ #include "group.h" #include "client_rule.h" #include "dns_conf_group.h" #include "domain_rule.h" #include "smartdns/lib/stringutil.h" #include int _config_group_begin(void *data, int argc, char *argv[]) { int opt = 0; const char *group_name = NULL; const char *inherit_group_name = NULL; if (argc < 2) { return -1; } /* clang-format off */ static struct option long_options[] = { {"inherit", required_argument, NULL, 'h'}, {NULL, no_argument, NULL, 0} }; /* clang-format on */ group_name = argv[1]; if (group_name[0] == '\0') { group_name = NULL; } while (1) { opt = getopt_long_only(argc, argv, "n", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'h': { inherit_group_name = optarg; break; } default: break; } } if (_config_current_group_push(group_name, inherit_group_name) != 0) { return -1; } return 0; } int _config_group_end(void *data, int argc, char *argv[]) { _config_current_group_pop(); return 0; } int _config_group_match(void *data, int argc, char *argv[]) { int opt = 0; int optind_last = 0; struct dns_conf_group_info *saved_group_info = _config_current_group(); const char *group_name = saved_group_info->group_name; char group_name_buf[DNS_MAX_CONF_CNAME_LEN]; /* clang-format off */ static struct option long_options[] = { {"domain", required_argument, NULL, 'd'}, {"client-ip", required_argument, NULL, 'c'}, {"group", required_argument, NULL, 'g'}, {NULL, no_argument, NULL, 0} }; /* clang-format on */ if (argc <= 1 || group_name == NULL) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } _config_set_current_group(_config_default_group()); for (int i = 1; i < argc - 1; i++) { if (strncmp(argv[i], "-g", sizeof("-g")) == 0 || strncmp(argv[i], "--group", sizeof("--group")) == 0 || strncmp(argv[i], "-group", sizeof("-group")) == 0) { safe_strncpy(group_name_buf, argv[i + 1], DNS_MAX_CONF_CNAME_LEN); group_name = group_name_buf; break; } } while (1) { opt = getopt_long_only(argc, argv, "g:", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'g': { group_name = optarg; break; } case 'd': { const char *domain = optarg; if (_conf_domain_rule_group(domain, group_name) != 0) { tlog(TLOG_ERROR, "set group match for domain %s failed.", optarg); goto errout; } break; } case 'c': { char *client_ip = optarg; if (_config_client_rule_group_add(client_ip, group_name) != 0) { tlog(TLOG_ERROR, "add group rule failed."); goto errout; } break; } default: if (optind > optind_last) { tlog(TLOG_WARN, "unknown group-match option: %s at '%s:%d'.", argv[optind - 1], conf_get_conf_file(), conf_get_current_lineno()); } break; } optind_last = optind; } _config_set_current_group(saved_group_info); return 0; errout: _config_set_current_group(saved_group_info); return -1; } ================================================ FILE: src/dns_conf/group.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_GROUP_H_ #define _DNS_CONF_GROUP_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_group_begin(void *data, int argc, char *argv[]); int _config_group_match(void *data, int argc, char *argv[]); int _config_group_end(void *data, int argc, char *argv[]); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/host_file.c ================================================ /************************************************************************* * * 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 . */ #include "host_file.h" #include "local_domain.h" #include "ptr.h" #include "set_file.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include #include #include struct dns_hosts_table dns_hosts_table; int dns_hosts_record_num; static int _conf_hosts_file_add(const char *file, void *priv) { FILE *fp = NULL; char line[MAX_LINE_LEN]; char ip[DNS_MAX_IPLEN]; char hostname[DNS_MAX_CNAME_LEN]; int ret = 0; int line_no = 0; fp = fopen(file, "r"); if (fp == NULL) { tlog(TLOG_WARN, "open file %s error, %s", file, strerror(errno)); return -1; } line_no = 0; while (fgets(line, MAX_LINE_LEN, fp)) { line_no++; int is_ptr_add = 0; char *token = strtok(line, " \t\n"); if (token == NULL) { continue; } safe_strncpy(ip, token, sizeof(ip) - 1); if (ip[0] == '#') { continue; } while ((token = strtok(NULL, " \t\n")) != NULL) { safe_strncpy(hostname, token, sizeof(hostname) - 1); char *skip_hostnames[] = { "*", }; int skip = 0; for (size_t i = 0; i < sizeof(skip_hostnames) / sizeof(skip_hostnames[0]); i++) { if (strncmp(hostname, skip_hostnames[i], DNS_MAX_CNAME_LEN - 1) == 0) { skip = 1; break; } } if (skip == 1) { continue; } ret = _conf_host_add(hostname, ip, DNS_HOST_TYPE_HOST, 0); if (ret != 0) { tlog(TLOG_WARN, "add hosts-file failed at '%s:%d'.", file, line_no); continue; } if (is_ptr_add == 1) { continue; } ret = _conf_ptr_add(hostname, ip, 0); if (ret != 0) { tlog(TLOG_WARN, "add hosts-file failed at '%s:%d'.", file, line_no); continue; } is_ptr_add = 1; } } fclose(fp); return 0; } int _config_hosts_file(void *data, int argc, char *argv[]) { const char *file_pattern = NULL; if (argc < 1) { return -1; } file_pattern = argv[1]; if (file_pattern == NULL) { return -1; } return _config_foreach_file(file_pattern, _conf_hosts_file_add, NULL); } void _config_host_table_init(void) { hash_init(dns_hosts_table.hosts); } void _config_host_table_destroy(int only_dynamic) { struct dns_hosts *host = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(dns_hosts_table.hosts, i, tmp, host, node) { if (only_dynamic != 0 && host->is_dynamic == 0) { continue; } hlist_del_init(&host->node); free(host); } dns_hosts_record_num = 0; } static struct dns_hosts *_dns_conf_get_hosts(const char *hostname, int dns_type) { uint32_t key = 0; struct dns_hosts *host = NULL; key = hash_string_case(hostname); key = jhash(&dns_type, sizeof(dns_type), key); hash_for_each_possible(dns_hosts_table.hosts, host, node, key) { if (host->dns_type != dns_type) { continue; } if (strncasecmp(host->domain, hostname, DNS_MAX_CNAME_LEN) != 0) { continue; } return host; } host = zalloc(1, sizeof(*host)); if (host == NULL) { goto errout; } safe_strncpy(host->domain, hostname, DNS_MAX_CNAME_LEN); host->dns_type = dns_type; host->is_soa = 1; hash_add(dns_hosts_table.hosts, &host->node, key); return host; errout: if (host) { free(host); } return NULL; } static int _conf_host_expand_local_domain(struct dns_hosts *host) { struct dns_hosts *host_expand = NULL; const char *local_domain = dns_conf_get_local_domain(); char domain[DNS_MAX_CNAME_LEN] = {0}; int ret; if (local_domain == NULL || local_domain[0] == '\0') { return 0; } if (strstr(host->domain, ".") != NULL) { // already has domain, skip return 0; } ret = snprintf(domain, sizeof(domain), "%s.%s", host->domain, local_domain); if (ret < 0 || ret >= (int)sizeof(domain)) { tlog(TLOG_WARN, "expand host %s with local domain %s failed, too long.", host->domain, local_domain); return -1; } host_expand = _dns_conf_get_hosts(domain, host->dns_type); if (host_expand == NULL) { goto errout; } host_expand->is_soa = host->is_soa; host_expand->is_dynamic = host->is_dynamic; host_expand->host_type = host->host_type; memcpy(host_expand->ipv6_addr, host->ipv6_addr, DNS_RR_AAAA_LEN); dns_hosts_record_num++; return 0; errout: return -1; } int _conf_host_add(const char *hostname, const char *ip, dns_hosts_type host_type, int is_dynamic) { struct dns_hosts *host = NULL; struct dns_hosts *host_other __attribute__((unused)); struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); int dns_type = 0; int dns_type_other = 0; if (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) { goto errout; } switch (addr.ss_family) { case AF_INET: dns_type = DNS_T_A; dns_type_other = DNS_T_AAAA; break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { dns_type = DNS_T_A; dns_type_other = DNS_T_AAAA; } else { dns_type = DNS_T_AAAA; dns_type_other = DNS_T_A; } } break; default: goto errout; break; } host = _dns_conf_get_hosts(hostname, dns_type); if (host == NULL) { goto errout; } if (is_dynamic == 1 && host->is_soa == 0 && host->is_dynamic == 0) { /* already set fixed PTR, skip */ return 0; } /* add this to return SOA when addr is not exist */ host_other = _dns_conf_get_hosts(hostname, dns_type_other); host->is_dynamic = is_dynamic; host->host_type = host_type; switch (addr.ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&addr; memcpy(host->ipv4_addr, &addr_in->sin_addr.s_addr, 4); host->is_soa = 0; } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { memcpy(host->ipv4_addr, addr_in6->sin6_addr.s6_addr + 12, 4); } else { memcpy(host->ipv6_addr, addr_in6->sin6_addr.s6_addr, 16); } host->is_soa = 0; } break; default: goto errout; } dns_hosts_record_num += 2; _conf_host_expand_local_domain(host); _conf_host_expand_local_domain(host_other); return 0; errout: return -1; } ================================================ FILE: src/dns_conf/host_file.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_HOST_FILE_H_ #define _DNS_CONF_HOST_FILE_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_hosts_file(void *data, int argc, char *argv[]); int _conf_host_add(const char *hostname, const char *ip, dns_hosts_type host_type, int is_dynamic); void _config_host_table_init(void); void _config_host_table_destroy(int only_dynamic); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/https_record.c ================================================ /************************************************************************* * * 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 . */ #include "https_record.h" #include "domain_rule.h" #include "get_domain.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" static int _conf_domain_rule_https_copy_alpn(char *alpn_data, int max_alpn_len, const char *alpn_str) { const char *ptr = NULL; int alpn_len = 0; char *alpn_len_ptr = NULL; char *alpn_ptr = NULL; int total_len = 0; ptr = alpn_str; alpn_len_ptr = alpn_data; alpn_ptr = alpn_data + 1; total_len++; while (*ptr != '\0') { total_len++; if (total_len > max_alpn_len) { return -1; } if (*ptr == ',') { *alpn_len_ptr = alpn_len; alpn_len = 0; alpn_len_ptr = alpn_ptr; ptr++; alpn_ptr++; continue; } *alpn_ptr = *ptr; alpn_len++; alpn_ptr++; ptr++; } *alpn_len_ptr = alpn_len; return total_len; } int _conf_domain_rule_https_record(const char *domain, const char *host) { struct dns_https_record_rule *https_record_rule = NULL; struct dns_https_record *record = NULL; enum domain_rule type = DOMAIN_RULE_HTTPS; char buff[4096]; int key_num = 0; char *keys[16]; char *value[16]; int priority = -1; /*mode_type, 0: alias mode, 1: service mode */ int mode_type = 0; int is_new = 0; safe_strncpy(buff, host, sizeof(buff)); https_record_rule = dns_conf_get_domain_rule(domain, type); if (https_record_rule == NULL) { https_record_rule = _new_dns_rule(type); if (https_record_rule == NULL) { goto errout; } INIT_LIST_HEAD(&https_record_rule->record_list); is_new = 1; } record = zalloc(1, sizeof(*record)); if (record == NULL) { goto errout; } if (conf_parse_key_values(buff, &key_num, keys, value) != 0) { tlog(TLOG_ERROR, "input format error, don't have key-value."); goto errout; } if (key_num < 1) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } for (int i = 0; i < key_num; i++) { const char *key = keys[i]; const char *val = value[i]; if (strncmp(key, "#", sizeof("#")) == 0) { if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_ADDR_HTTPS_SOA, 0) != 0) { goto errout; } break; } else if (strncmp(key, "-", sizeof("-")) == 0) { if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_ADDR_HTTPS_IGN, 0) != 0) { goto errout; } if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_ADDR_IGN, 0) != 0) { goto errout; } } else if (strncmp(key, "target", sizeof("target")) == 0) { safe_strncpy(record->target, val, DNS_MAX_CONF_CNAME_LEN); record->enable = 1; } else if (strncmp(key, "noipv4hint", sizeof("noipv4hint")) == 0) { https_record_rule->filter.no_ipv4hint = 1; } else if (strncmp(key, "noipv6hint", sizeof("noipv6hint")) == 0) { https_record_rule->filter.no_ipv6hint = 1; } else if (strncmp(key, "noiphint", sizeof("noiphint")) == 0) { https_record_rule->filter.no_ipv4hint = 1; https_record_rule->filter.no_ipv6hint = 1; } else if (strncmp(key, "noech", sizeof("noech")) == 0) { https_record_rule->filter.no_ech = 1; } else { mode_type = 1; record->enable = 1; if (strncmp(key, "priority", sizeof("priority")) == 0) { priority = atoi(val); } else if (strncmp(key, "port", sizeof("port")) == 0) { record->port = atoi(val); } else if (strncmp(key, "alpn", sizeof("alpn")) == 0) { int alpn_len = _conf_domain_rule_https_copy_alpn(record->alpn, DNS_MAX_ALPN_LEN, val); if (alpn_len <= 0) { tlog(TLOG_ERROR, "invalid option value for %s.", key); goto errout; } record->alpn_len = alpn_len; } else if (strncmp(key, "ech", sizeof("ech")) == 0) { int ech_len = SSL_base64_decode(val, record->ech, DNS_MAX_ECH_LEN); if (ech_len < 0) { tlog(TLOG_ERROR, "invalid option value for %s.", key); goto errout; } record->ech_len = ech_len; } else if (strncmp(key, "ipv4hint", sizeof("ipv4hint")) == 0) { int addr_len = DNS_RR_A_LEN; if (get_raw_addr_by_ip(val, record->ipv4_addr, &addr_len) != 0) { tlog(TLOG_ERROR, "invalid option value for %s, value: %s", key, val); goto errout; } if (addr_len != DNS_RR_A_LEN) { tlog(TLOG_ERROR, "invalid option value for %s, value: %s", key, val); goto errout; } record->has_ipv4 = 1; } else if (strncmp(key, "ipv6hint", sizeof("ipv6hint")) == 0) { int addr_len = DNS_RR_AAAA_LEN; if (get_raw_addr_by_ip(val, record->ipv6_addr, &addr_len) != 0) { tlog(TLOG_ERROR, "invalid option value for %s, value: %s", key, val); goto errout; } if (addr_len != DNS_RR_AAAA_LEN) { tlog(TLOG_ERROR, "invalid option value for %s, value: %s", key, val); goto errout; } record->has_ipv6 = 1; } else { tlog(TLOG_WARN, "invalid parameter %s for https-record.", key); continue; } } } if (mode_type == 0) { if (priority < 0) { priority = 0; } } else { if (priority < 0) { priority = 1; } else if (priority == 0) { tlog(TLOG_WARN, "invalid priority %d for https-record.", priority); goto errout; } } record->priority = priority; list_add_tail(&record->list, &https_record_rule->record_list); if (is_new) { if (_config_domain_rule_add(domain, type, https_record_rule) != 0) { goto errout; } _dns_rule_put(&https_record_rule->head); https_record_rule = NULL; } return 0; errout: if (is_new && https_record_rule) { _dns_rule_put(&https_record_rule->head); } if (record && record->list.next == NULL) { free(record); } return -1; } int _config_https_record(void *data, int argc, char *argv[]) { char *value = NULL; char domain[DNS_MAX_CONF_CNAME_LEN]; int ret = -1; if (argc < 2) { goto errout; } value = argv[1]; if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) { goto errout; } ret = _conf_domain_rule_https_record(domain, value); if (ret != 0) { goto errout; } return 0; errout: tlog(TLOG_ERROR, "add https-record %s:%s failed", domain, value); return -1; } ================================================ FILE: src/dns_conf/https_record.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_HTTPS_RECORD_H_ #define _DNS_CONF_HTTPS_RECORD_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_https_record(void *data, int argc, char *argv[]); int _conf_domain_rule_https_record(const char *domain, const char *host); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/ip_alias.c ================================================ /************************************************************************* * * 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 . */ #include "ip_alias.h" #include "ip_rule.h" static int _config_ip_alias_add_ip_callback(const char *ip_cidr, void *priv) { return _config_ip_rule_alias_add_ip(ip_cidr, (struct ip_rule_alias *)priv); } int _conf_ip_alias(const char *ip_cidr, const char *ips) { struct ip_rule_alias *ip_alias = NULL; char *target_ips = NULL; int ret = 0; if (ip_cidr == NULL || ips == NULL) { goto errout; } ip_alias = _new_dns_ip_rule(IP_RULE_ALIAS); if (ip_alias == NULL) { goto errout; } if (strncmp(ips, "ip-set:", sizeof("ip-set:") - 1) == 0) { if (_config_ip_rule_set_each(ips + sizeof("ip-set:") - 1, _config_ip_alias_add_ip_callback, ip_alias) != 0) { goto errout; } } else { target_ips = strdup(ips); if (target_ips == NULL) { goto errout; } for (char *tok = strtok(target_ips, ","); tok != NULL; tok = strtok(NULL, ",")) { ret = _config_ip_rule_alias_add_ip(tok, ip_alias); if (ret != 0) { goto errout; } } } if (_config_ip_rule_add(ip_cidr, IP_RULE_ALIAS, ip_alias) != 0) { goto errout; } _dns_ip_rule_put(&ip_alias->head); if (target_ips) { free(target_ips); } return 0; errout: if (ip_alias) { _dns_ip_rule_put(&ip_alias->head); } if (target_ips) { free(target_ips); } return -1; } int _config_ip_alias(void *data, int argc, char *argv[]) { if (argc <= 2) { return -1; } return _conf_ip_alias(argv[1], argv[2]); } ================================================ FILE: src/dns_conf/ip_alias.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_IP_ALIAS_H_ #define _DNS_CONF_IP_ALIAS_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _conf_ip_alias(const char *ip_cidr, const char *ips); int _config_ip_alias(void *data, int argc, char *argv[]); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/ip_rule.c ================================================ /************************************************************************* * * 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 . */ #include "ip_rule.h" #include "dns_conf_group.h" #include "ip_alias.h" #include "set_file.h" #include "smartdns/util.h" #include int _config_ip_rule_set_each(const char *ip_set, set_rule_add_func callback, void *priv) { struct dns_ip_set_name_list *set_name_list = NULL; struct dns_ip_set_name *set_name_item = NULL; uint32_t key = 0; key = hash_string(ip_set); hash_for_each_possible(dns_ip_set_name_table.names, set_name_list, node, key) { if (strcmp(set_name_list->name, ip_set) == 0) { break; } } if (set_name_list == NULL) { tlog(TLOG_WARN, "ip set %s not found.", ip_set); return -1; } list_for_each_entry(set_name_item, &set_name_list->set_name_list, list) { switch (set_name_item->type) { case DNS_IP_SET_LIST: if (_config_set_rule_each_from_list(set_name_item->file, callback, priv) != 0) { return -1; } break; default: tlog(TLOG_WARN, "ip set %s type %d not support.", set_name_list->name, set_name_item->type); break; } } return 0; } static void _dns_iplist_ip_address_add(struct dns_iplist_ip_addresses *iplist, unsigned char addr[], int addr_len) { struct dns_iplist_ip_address *new_ipaddr = realloc(iplist->ipaddr, (iplist->ipaddr_num + 1) * sizeof(struct dns_iplist_ip_address)); if (new_ipaddr == NULL) { return; } iplist->ipaddr = new_ipaddr; memset(&iplist->ipaddr[iplist->ipaddr_num], 0, sizeof(struct dns_iplist_ip_address)); iplist->ipaddr[iplist->ipaddr_num].addr_len = addr_len; memcpy(iplist->ipaddr[iplist->ipaddr_num].addr, addr, addr_len); iplist->ipaddr_num++; } int _config_ip_rules(void *data, int argc, char *argv[]) { int opt = 0; int optind_last = 0; char *ip_cidr = argv[1]; /* clang-format off */ static struct option long_options[] = { {"blacklist-ip", no_argument, NULL, 'b'}, {"whitelist-ip", no_argument, NULL, 'w'}, {"bogus-nxdomain", no_argument, NULL, 'n'}, {"ignore-ip", no_argument, NULL, 'i'}, {"ip-alias", required_argument, NULL, 'a'}, {NULL, no_argument, NULL, 0} }; /* clang-format on */ if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } /* process extra options */ optind = 1; optind_last = 1; while (1) { opt = getopt_long_only(argc, argv, "", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'b': { if (_config_ip_rule_flag_set(ip_cidr, IP_RULE_FLAG_BLACKLIST, 0) != 0) { goto errout; } break; } case 'w': { if (_config_ip_rule_flag_set(ip_cidr, IP_RULE_FLAG_WHITELIST, 0) != 0) { goto errout; } break; } case 'n': { if (_config_ip_rule_flag_set(ip_cidr, IP_RULE_FLAG_BOGUS, 0) != 0) { goto errout; } break; } case 'i': { if (_config_ip_rule_flag_set(ip_cidr, IP_RULE_FLAG_IP_IGNORE, 0) != 0) { goto errout; } break; } case 'a': { if (_conf_ip_alias(ip_cidr, optarg) != 0) { goto errout; } break; } default: if (optind > optind_last) { tlog(TLOG_WARN, "unknown ip-rules option: %s at '%s:%d'.", argv[optind - 1], conf_get_conf_file(), conf_get_current_lineno()); } break; } optind_last = optind; } return 0; errout: return -1; } static int _config_ip_rules_free(struct dns_ip_rules *ip_rules) { int i = 0; if (ip_rules == NULL) { return 0; } for (i = 0; i < IP_RULE_MAX; i++) { if (ip_rules->rules[i] == NULL) { continue; } _dns_ip_rule_put(ip_rules->rules[i]); ip_rules->rules[i] = NULL; } free(ip_rules); return 0; } static radix_node_t *_create_addr_node(const char *addr) { radix_node_t *node = NULL; void *p = NULL; prefix_t prefix; const char *errmsg = NULL; radix_tree_t *tree = NULL; p = prefix_pton(addr, -1, &prefix, &errmsg); if (p == NULL) { return NULL; } switch (prefix.family) { case AF_INET: tree = _config_current_rule_group()->address_rule.ipv4; break; case AF_INET6: tree = _config_current_rule_group()->address_rule.ipv6; break; default: return NULL; } node = radix_lookup(tree, &prefix); return node; } static int _config_ip_rule_flag_callback(const char *ip_cidr, void *priv) { struct dns_set_rule_flags_callback_args *args = (struct dns_set_rule_flags_callback_args *)priv; return _config_ip_rule_flag_set(ip_cidr, args->flags, args->is_clear_flag); } int _config_ip_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear) { struct dns_ip_rules *ip_rules = NULL; struct dns_ip_rules *add_ip_rules = NULL; struct ip_rule_flags *ip_rule_flags = NULL; radix_node_t *node = NULL; if (strncmp(ip_cidr, "ip-set:", sizeof("ip-set:") - 1) == 0) { struct dns_set_rule_flags_callback_args args; args.flags = flag; args.is_clear_flag = is_clear; return _config_ip_rule_set_each(ip_cidr + sizeof("ip-set:") - 1, _config_ip_rule_flag_callback, &args); } /* Get existing or create domain rule */ node = _create_addr_node(ip_cidr); if (node == NULL) { tlog(TLOG_ERROR, "create addr node failed."); goto errout; } ip_rules = node->data; if (ip_rules == NULL) { add_ip_rules = zalloc(1, sizeof(*add_ip_rules)); if (add_ip_rules == NULL) { goto errout; } ip_rules = add_ip_rules; node->data = ip_rules; } /* add new rule to domain */ if (ip_rules->rules[IP_RULE_FLAGS] == NULL) { ip_rule_flags = _new_dns_ip_rule(IP_RULE_FLAGS); ip_rule_flags->flags = 0; ip_rules->rules[IP_RULE_FLAGS] = &ip_rule_flags->head; } ip_rule_flags = container_of(ip_rules->rules[IP_RULE_FLAGS], struct ip_rule_flags, head); if (is_clear == false) { ip_rule_flags->flags |= flag; } else { ip_rule_flags->flags &= ~flag; } ip_rule_flags->is_flag_set |= flag; return 0; errout: if (add_ip_rules) { free(add_ip_rules); } tlog(TLOG_ERROR, "set ip %s flags failed", ip_cidr); return 0; } static int _config_ip_rule_add_callback(const char *ip_cidr, void *priv) { struct dns_set_rule_add_callback_args *args = (struct dns_set_rule_add_callback_args *)priv; return _config_ip_rule_add(ip_cidr, args->type, args->rule); } int _config_ip_rule_add(const char *ip_cidr, enum ip_rule type, void *rule) { struct dns_ip_rules *ip_rules = NULL; struct dns_ip_rules *add_ip_rules = NULL; radix_node_t *node = NULL; if (ip_cidr == NULL) { goto errout; } if (type >= IP_RULE_MAX) { goto errout; } if (strncmp(ip_cidr, "ip-set:", sizeof("ip-set:") - 1) == 0) { struct dns_set_rule_add_callback_args args; args.type = type; args.rule = rule; return _config_ip_rule_set_each(ip_cidr + sizeof("ip-set:") - 1, _config_ip_rule_add_callback, &args); } /* Get existing or create domain rule */ node = _create_addr_node(ip_cidr); if (node == NULL) { tlog(TLOG_ERROR, "create addr node failed."); goto errout; } ip_rules = node->data; if (ip_rules == NULL) { add_ip_rules = zalloc(1, sizeof(*add_ip_rules)); if (add_ip_rules == NULL) { goto errout; } ip_rules = add_ip_rules; node->data = ip_rules; } /* add new rule to domain */ if (ip_rules->rules[type]) { _dns_ip_rule_put(ip_rules->rules[type]); ip_rules->rules[type] = NULL; } ip_rules->rules[type] = rule; _dns_ip_rule_get(rule); return 0; errout: if (add_ip_rules) { free(add_ip_rules); } tlog(TLOG_ERROR, "add ip %s rule failed", ip_cidr); return -1; } static void *_new_dns_ip_rule_ext(enum ip_rule ip_rule, int ext_size) { struct dns_ip_rule *rule; int size = 0; if (ip_rule >= IP_RULE_MAX) { return NULL; } switch (ip_rule) { case IP_RULE_FLAGS: size = sizeof(struct ip_rule_flags); break; case IP_RULE_ALIAS: size = sizeof(struct ip_rule_alias); break; default: return NULL; } size += ext_size; rule = zalloc(1, size); if (!rule) { return NULL; } rule->rule = ip_rule; atomic_set(&rule->refcnt, 1); return rule; } void *_new_dns_ip_rule(enum ip_rule ip_rule) { return _new_dns_ip_rule_ext(ip_rule, 0); } void _dns_ip_rule_get(struct dns_ip_rule *rule) { atomic_inc(&rule->refcnt); } void _dns_ip_rule_put(struct dns_ip_rule *rule) { if (atomic_dec_and_test(&rule->refcnt)) { if (rule->rule == IP_RULE_ALIAS) { struct ip_rule_alias *alias = container_of(rule, struct ip_rule_alias, head); if (alias->ip_alias.ipaddr) { free(alias->ip_alias.ipaddr); alias->ip_alias.ipaddr = NULL; alias->ip_alias.ipaddr_num = 0; } } free(rule); } } int _config_ip_rule_alias_add_ip(const char *ip, struct ip_rule_alias *ip_alias) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); unsigned char *paddr = NULL; int ret = 0; ret = getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len); if (ret != 0) { tlog(TLOG_ERROR, "ip is invalid: %s", ip); goto errout; } switch (addr.ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&addr; paddr = (unsigned char *)&(addr_in->sin_addr.s_addr); _dns_iplist_ip_address_add(&ip_alias->ip_alias, paddr, DNS_RR_A_LEN); } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { paddr = addr_in6->sin6_addr.s6_addr + 12; _dns_iplist_ip_address_add(&ip_alias->ip_alias, paddr, DNS_RR_A_LEN); } else { paddr = addr_in6->sin6_addr.s6_addr; _dns_iplist_ip_address_add(&ip_alias->ip_alias, paddr, DNS_RR_AAAA_LEN); } } break; default: goto errout; break; } return 0; errout: return -1; } int _config_blacklist_ip(void *data, int argc, char *argv[]) { if (argc <= 1) { return -1; } return _config_ip_rule_flag_set(argv[1], IP_RULE_FLAG_BLACKLIST, 0); } int _config_bogus_nxdomain(void *data, int argc, char *argv[]) { if (argc <= 1) { return -1; } return _config_ip_rule_flag_set(argv[1], IP_RULE_FLAG_BOGUS, 0); } int _config_ip_ignore(void *data, int argc, char *argv[]) { if (argc <= 1) { return -1; } return _config_ip_rule_flag_set(argv[1], IP_RULE_FLAG_IP_IGNORE, 0); } int _config_whitelist_ip(void *data, int argc, char *argv[]) { if (argc <= 1) { return -1; } return _config_ip_rule_flag_set(argv[1], IP_RULE_FLAG_WHITELIST, 0); } void _config_ip_iter_free(radix_node_t *node, void *cbctx) { struct dns_ip_rules *ip_rules = NULL; if (node == NULL) { return; } if (node->data == NULL) { return; } ip_rules = node->data; _config_ip_rules_free(ip_rules); node->data = NULL; } ================================================ FILE: src/dns_conf/ip_rule.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_IP_RULE_H_ #define _DNS_CONF_IP_RULE_H_ #include "dns_conf.h" #include "set_file.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _config_ip_iter_free(radix_node_t *node, void *cbctx); int _config_ip_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear); int _config_ip_rule_set_each(const char *ip_set, set_rule_add_func callback, void *priv); int _config_blacklist_ip(void *data, int argc, char *argv[]); int _config_bogus_nxdomain(void *data, int argc, char *argv[]); int _config_ip_ignore(void *data, int argc, char *argv[]); int _config_whitelist_ip(void *data, int argc, char *argv[]); int _config_ip_rules(void *data, int argc, char *argv[]); int _config_ip_rule_alias_add_ip(const char *ip, struct ip_rule_alias *ip_alias); int _config_ip_rule_add(const char *ip_cidr, enum ip_rule type, void *rule); void *_new_dns_ip_rule(enum ip_rule ip_rule); void _dns_ip_rule_get(struct dns_ip_rule *rule); void _dns_ip_rule_put(struct dns_ip_rule *rule); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/ip_set.c ================================================ /************************************************************************* * * 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 . */ #include "ip_set.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include #include #include struct dns_ip_set_name_table dns_ip_set_name_table; int _config_ip_set(void *data, int argc, char *argv[]) { int opt = 0; uint32_t key = 0; struct dns_ip_set_name *ip_set = NULL; struct dns_ip_set_name_list *ip_set_name_list = NULL; char set_name[DNS_MAX_CNAME_LEN] = {0}; /* clang-format off */ static struct option long_options[] = { {"name", required_argument, NULL, 'n'}, {"type", required_argument, NULL, 't'}, {"file", required_argument, NULL, 'f'}, {NULL, 0, NULL, 0} }; if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } ip_set = zalloc(1, sizeof(*ip_set)); if (ip_set == NULL) { tlog(TLOG_ERROR, "cannot malloc memory."); goto errout; } INIT_LIST_HEAD(&ip_set->list); optind = 1; while (1) { opt = getopt_long_only(argc, argv, "n:t:f:", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'n': safe_strncpy(set_name, optarg, DNS_MAX_CNAME_LEN); break; case 't': { const char *type = optarg; if (strncmp(type, "list", 5) == 0) { ip_set->type = DNS_IP_SET_LIST; } else { tlog(TLOG_ERROR, "invalid domain set type."); goto errout; } break; } case 'f': conf_get_conf_fullpath(optarg, ip_set->file, DNS_MAX_PATH); break; default: break; } } /* clang-format on */ if (access(ip_set->file, F_OK) != 0) { tlog(TLOG_ERROR, "ip set file %s not readable. %s", ip_set->file, strerror(errno)); goto errout; } if (set_name[0] == 0 || ip_set->file[0] == 0) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } key = hash_string(set_name); hash_for_each_possible(dns_ip_set_name_table.names, ip_set_name_list, node, key) { if (strcmp(ip_set_name_list->name, set_name) == 0) { break; } } if (ip_set_name_list == NULL) { ip_set_name_list = zalloc(1, sizeof(*ip_set_name_list)); if (ip_set_name_list == NULL) { tlog(TLOG_ERROR, "cannot malloc memory."); goto errout; } INIT_LIST_HEAD(&ip_set_name_list->set_name_list); safe_strncpy(ip_set_name_list->name, set_name, DNS_MAX_CNAME_LEN); hash_add(dns_ip_set_name_table.names, &ip_set_name_list->node, key); } list_add_tail(&ip_set->list, &ip_set_name_list->set_name_list); return 0; errout: if (ip_set) { free(ip_set); } if (ip_set_name_list != NULL) { free(ip_set_name_list); } return -1; } void _config_ip_set_name_table_init(void) { hash_init(dns_ip_set_name_table.names); } void _config_ip_set_name_table_destroy(void) { struct dns_ip_set_name_list *set_name_list = NULL; struct hlist_node *tmp = NULL; struct dns_ip_set_name *set_name = NULL; struct dns_ip_set_name *tmp1 = NULL; unsigned long i = 0; hash_for_each_safe(dns_ip_set_name_table.names, i, tmp, set_name_list, node) { hlist_del_init(&set_name_list->node); list_for_each_entry_safe(set_name, tmp1, &set_name_list->set_name_list, list) { list_del(&set_name->list); free(set_name); } free(set_name_list); } } ================================================ FILE: src/dns_conf/ip_set.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_IP_SET_H_ #define _DNS_CONF_IP_SET_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_ip_set(void *data, int argc, char *argv[]); void _config_ip_set_name_table_init(void); void _config_ip_set_name_table_destroy(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/ipset.c ================================================ /************************************************************************* * * 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 . */ #include "ipset.h" #include "dns_conf_group.h" #include "domain_rule.h" #include "get_domain.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" /* ipset */ struct dns_ipset_table { DECLARE_HASHTABLE(ipset, 8); }; static struct dns_ipset_table dns_ipset_table; int _config_ipset_init(void) { hash_init(dns_ipset_table.ipset); return 0; } void _config_ipset_table_destroy(void) { struct dns_ipset_name *ipset_name = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(dns_ipset_table.ipset, i, tmp, ipset_name, node) { hlist_del_init(&ipset_name->node); free(ipset_name); } } const char *_dns_conf_get_ipset(const char *ipsetname) { uint32_t key = 0; struct dns_ipset_name *ipset_name = NULL; key = hash_string(ipsetname); hash_for_each_possible(dns_ipset_table.ipset, ipset_name, node, key) { if (strncmp(ipset_name->ipsetname, ipsetname, DNS_MAX_IPSET_NAMELEN) == 0) { return ipset_name->ipsetname; } } ipset_name = zalloc(1, sizeof(*ipset_name)); if (ipset_name == NULL) { goto errout; } key = hash_string(ipsetname); safe_strncpy(ipset_name->ipsetname, ipsetname, DNS_MAX_IPSET_NAMELEN); hash_add(dns_ipset_table.ipset, &ipset_name->node, key); return ipset_name->ipsetname; errout: if (ipset_name) { free(ipset_name); } return NULL; } int _conf_domain_rule_ipset(char *domain, const char *ipsetname) { struct dns_ipset_rule *ipset_rule = NULL; const char *ipset = NULL; char *copied_name = NULL; enum domain_rule type = 0; int ignore_flag = 0; int ret = -1; copied_name = strdup(ipsetname); if (copied_name == NULL) { goto errout; } for (char *tok = strtok(copied_name, ","); tok; tok = strtok(NULL, ",")) { if (tok[0] == '#') { if (strncmp(tok, "#6:", 3U) == 0) { type = DOMAIN_RULE_IPSET_IPV6; ignore_flag = DOMAIN_FLAG_IPSET_IPV6_IGN; } else if (strncmp(tok, "#4:", 3U) == 0) { type = DOMAIN_RULE_IPSET_IPV4; ignore_flag = DOMAIN_FLAG_IPSET_IPV4_IGN; } else { goto errout; } tok += 3; } else { type = DOMAIN_RULE_IPSET; ignore_flag = DOMAIN_FLAG_IPSET_IGN; } if (strncmp(tok, "-", 1) == 0) { _config_domain_rule_flag_set(domain, ignore_flag, 0); continue; } /* new ipset domain */ ipset = _dns_conf_get_ipset(tok); if (ipset == NULL) { goto errout; } ipset_rule = _new_dns_rule(type); if (ipset_rule == NULL) { goto errout; } ipset_rule->ipsetname = ipset; if (_config_domain_rule_add(domain, type, ipset_rule) != 0) { goto errout; } _dns_rule_put(&ipset_rule->head); ipset_rule = NULL; } ret = 0; goto clear; errout: tlog(TLOG_ERROR, "add ipset %s failed", ipsetname); if (ipset_rule) { _dns_rule_put(&ipset_rule->head); } clear: if (copied_name) { free(copied_name); } return ret; } static int _config_ipset_setvalue(struct dns_ipset_names *ipsets, const char *ipsetvalue) { char *copied_name = NULL; const char *ipset = NULL; struct dns_ipset_rule *ipset_rule_array[2] = {NULL, NULL}; char *ipset_rule_enable_array[2] = {NULL, NULL}; int ipset_num = 0; copied_name = strdup(ipsetvalue); if (copied_name == NULL) { goto errout; } for (char *tok = strtok(copied_name, ","); tok && ipset_num <= 2; tok = strtok(NULL, ",")) { if (tok[0] == '#') { if (strncmp(tok, "#6:", 3U) == 0) { ipset_rule_array[ipset_num] = &ipsets->ipv6; ipset_rule_enable_array[ipset_num] = &ipsets->ipv6_enable; ipset_num++; } else if (strncmp(tok, "#4:", 3U) == 0) { ipset_rule_array[ipset_num] = &ipsets->ipv4; ipset_rule_enable_array[ipset_num] = &ipsets->ipv4_enable; ipset_num++; } else { goto errout; } tok += 3; } if (ipset_num == 0) { ipset_rule_array[0] = &ipsets->inet; ipset_rule_enable_array[0] = &ipsets->inet_enable; ipset_num = 1; } if (strncmp(tok, "-", 1) == 0) { continue; } /* new ipset domain */ ipset = _dns_conf_get_ipset(tok); if (ipset == NULL) { goto errout; } for (int i = 0; i < ipset_num; i++) { ipset_rule_array[i]->ipsetname = ipset; *ipset_rule_enable_array[i] = 1; } ipset_num = 0; } free(copied_name); return 0; errout: if (copied_name) { free(copied_name); } return 0; } int _config_ipset(void *data, int argc, char *argv[]) { char domain[DNS_MAX_CONF_CNAME_LEN]; char *value = argv[1]; int ret = 0; if (argc <= 1) { goto errout; } if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) { goto errout; } ret = _conf_domain_rule_ipset(domain, value); if (ret != 0) { goto errout; } return 0; errout: tlog(TLOG_WARN, "add ipset %s failed.", value); return ret; } int _config_ipset_no_speed(void *data, int argc, char *argv[]) { char *ipsetname = argv[1]; if (argc <= 1) { goto errout; } if (_config_ipset_setvalue(&_config_current_rule_group()->ipset_nftset.ipset_no_speed, ipsetname) != 0) { goto errout; } return 0; errout: tlog(TLOG_ERROR, "add ipset-no-speed %s failed", ipsetname); return 0; } ================================================ FILE: src/dns_conf/ipset.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_IPSET_H_ #define _DNS_CONF_IPSET_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ const char *_dns_conf_get_ipset(const char *ipsetname); int _config_ipset_init(void); void _config_ipset_table_destroy(void); int _conf_domain_rule_ipset(char *domain, const char *ipsetname); int _config_ipset_no_speed(void *data, int argc, char *argv[]); int _config_ipset(void *data, int argc, char *argv[]); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/local_domain.c ================================================ /************************************************************************* * * 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 . */ #include "local_domain.h" #include "domain_rule.h" #include "nameserver.h" #include "smartdns/lib/stringutil.h" static char local_domain[DNS_MAX_CNAME_LEN] = {0}; const char *dns_conf_get_local_domain(void) { return local_domain; } int _config_local_domain(void *data, int argc, char *argv[]) { if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); return -1; } const char *domain = argv[1]; if (local_domain[0] != '\0') { _config_domain_rule_remove(local_domain, DOMAIN_RULE_NAMESERVER); local_domain[0] = '\0'; } if (domain[0] == '\0' || strncmp(domain, "-", sizeof("-")) == 0) { return 0; } safe_strncpy(local_domain, domain, sizeof(local_domain)); _conf_domain_rule_nameserver(local_domain, DNS_SERVER_GROUP_MDNS); return 0; } ================================================ FILE: src/dns_conf/local_domain.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_LOCAL_DOMAIN_H_ #define _DNS_CONF_LOCAL_DOMAIN_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ const char *dns_conf_get_local_domain(void); int _config_local_domain(void *data, int argc, char *argv[]); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/nameserver.c ================================================ /************************************************************************* * * 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 . */ #include "nameserver.h" #include "domain_rule.h" #include "get_domain.h" #include "server_group.h" int _conf_domain_rule_nameserver(const char *domain, const char *group_name) { struct dns_nameserver_rule *nameserver_rule = NULL; const char *group = NULL; if (strncmp(group_name, "-", sizeof("-")) != 0) { group = _dns_conf_get_group_name(group_name); if (group == NULL) { goto errout; } nameserver_rule = _new_dns_rule(DOMAIN_RULE_NAMESERVER); if (nameserver_rule == NULL) { goto errout; } nameserver_rule->group_name = group; } else { /* ignore this domain */ if (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_NAMESERVER_IGNORE, 0) != 0) { goto errout; } return 0; } if (_config_domain_rule_add(domain, DOMAIN_RULE_NAMESERVER, nameserver_rule) != 0) { goto errout; } _dns_rule_put(&nameserver_rule->head); return 0; errout: if (nameserver_rule) { _dns_rule_put(&nameserver_rule->head); } tlog(TLOG_ERROR, "add nameserver %s, %s failed", domain, group_name); return 0; } int _config_nameserver(void *data, int argc, char *argv[]) { char domain[DNS_MAX_CONF_CNAME_LEN]; char *value = argv[1]; if (argc <= 1) { goto errout; } if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) { goto errout; } return _conf_domain_rule_nameserver(domain, value); errout: tlog(TLOG_ERROR, "add nameserver %s failed", value); return 0; } ================================================ FILE: src/dns_conf/nameserver.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_NAMESERVER_H_ #define _DNS_CONF_NAMESERVER_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_nameserver(void *data, int argc, char *argv[]); int _conf_domain_rule_nameserver(const char *domain, const char *group_name); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/nftset.c ================================================ /************************************************************************* * * 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 . */ #include "nftset.h" #include "dns_conf_group.h" #include "domain_rule.h" #include "get_domain.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" struct dns_nftset_table { DECLARE_HASHTABLE(nftset, 8); }; static struct dns_nftset_table dns_nftset_table; void _config_nftset_table_destroy(void) { struct dns_nftset_name *nftset = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(dns_nftset_table.nftset, i, tmp, nftset, node) { hlist_del_init(&nftset->node); free(nftset); } } const struct dns_nftset_name *_dns_conf_get_nftable(const char *familyname, const char *tablename, const char *setname) { uint32_t key = 0; struct dns_nftset_name *nftset_name = NULL; if (familyname == NULL || tablename == NULL || setname == NULL) { return NULL; } const char *hasher[4] = {familyname, tablename, setname, NULL}; key = hash_string_array(hasher); hash_for_each_possible(dns_nftset_table.nftset, nftset_name, node, key) { if (strncmp(nftset_name->nftfamilyname, familyname, DNS_MAX_NFTSET_FAMILYLEN) == 0 && strncmp(nftset_name->nfttablename, tablename, DNS_MAX_NFTSET_NAMELEN) == 0 && strncmp(nftset_name->nftsetname, setname, DNS_MAX_NFTSET_NAMELEN) == 0) { return nftset_name; } } nftset_name = zalloc(1, sizeof(*nftset_name)); if (nftset_name == NULL) { goto errout; } safe_strncpy(nftset_name->nftfamilyname, familyname, DNS_MAX_NFTSET_FAMILYLEN); safe_strncpy(nftset_name->nfttablename, tablename, DNS_MAX_NFTSET_NAMELEN); safe_strncpy(nftset_name->nftsetname, setname, DNS_MAX_NFTSET_NAMELEN); hash_add(dns_nftset_table.nftset, &nftset_name->node, key); return nftset_name; errout: if (nftset_name) { free(nftset_name); } return NULL; } int _conf_domain_rule_nftset(char *domain, const char *nftsetname) { struct dns_nftset_rule *nftset_rule = NULL; const struct dns_nftset_name *nftset = NULL; char *copied_name = NULL; enum domain_rule type = 0; int ignore_flag = 0; char *setname = NULL; char *tablename = NULL; char *family = NULL; int ret = -1; copied_name = strdup(nftsetname); if (copied_name == NULL) { goto errout; } for (char *tok = strtok(copied_name, ","); tok; tok = strtok(NULL, ",")) { char *saveptr = NULL; char *tok_set = NULL; nftset_rule = NULL; if (strncmp(tok, "#4:", 3U) == 0) { type = DOMAIN_RULE_NFTSET_IP; ignore_flag = DOMAIN_FLAG_NFTSET_IP_IGN; } else if (strncmp(tok, "#6:", 3U) == 0) { type = DOMAIN_RULE_NFTSET_IP6; ignore_flag = DOMAIN_FLAG_NFTSET_IP6_IGN; } else if (strncmp(tok, "-", 2U) == 0) { _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NFTSET_INET_IGN, 0); continue; } else { goto errout; } tok_set = tok + 3; if (strncmp(tok_set, "-", 2U) == 0) { _config_domain_rule_flag_set(domain, ignore_flag, 0); continue; } family = strtok_r(tok_set, "#", &saveptr); if (family == NULL) { goto errout; } tablename = strtok_r(NULL, "#", &saveptr); if (tablename == NULL) { goto errout; } setname = strtok_r(NULL, "#", &saveptr); if (setname == NULL) { goto errout; } /* new nftset domain */ nftset = _dns_conf_get_nftable(family, tablename, setname); if (nftset == NULL) { goto errout; } nftset_rule = _new_dns_rule(type); if (nftset_rule == NULL) { goto errout; } nftset_rule->nfttablename = nftset->nfttablename; nftset_rule->nftsetname = nftset->nftsetname; nftset_rule->familyname = nftset->nftfamilyname; if (_config_domain_rule_add(domain, type, nftset_rule) != 0) { goto errout; } _dns_rule_put(&nftset_rule->head); nftset_rule = NULL; } ret = 0; goto clear; errout: tlog(TLOG_ERROR, "add nftset %s %s failed.", domain, nftsetname); if (nftset_rule) { _dns_rule_put(&nftset_rule->head); } clear: if (copied_name) { free(copied_name); } return ret; } static int _config_nftset_setvalue(struct dns_nftset_names *nftsets, const char *nftsetvalue) { const struct dns_nftset_name *nftset = NULL; char *copied_name = NULL; int nftset_num = 0; char *setname = NULL; char *tablename = NULL; char *family = NULL; int ret = -1; struct dns_nftset_rule *nftset_rule_array[2] = {NULL, NULL}; char *nftset_rule_enable_array[2] = {NULL, NULL}; if (nftsetvalue == NULL) { goto errout; } copied_name = strdup(nftsetvalue); if (copied_name == NULL) { goto errout; } for (char *tok = strtok(copied_name, ","); tok && nftset_num <= 2; tok = strtok(NULL, ",")) { char *saveptr = NULL; char *tok_set = NULL; if (strncmp(tok, "#4:", 3U) == 0) { nftsets->ip_enable = 1; nftset_rule_array[nftset_num] = &nftsets->ip; nftset_rule_enable_array[nftset_num] = &nftsets->ip_enable; nftset_num++; } else if (strncmp(tok, "#6:", 3U) == 0) { nftset_rule_enable_array[nftset_num] = &nftsets->ip6_enable; nftset_rule_array[nftset_num] = &nftsets->ip6; nftset_num++; } else if (strncmp(tok, "-", 2U) == 0) { continue; continue; } else { goto errout; } tok_set = tok + 3; if (nftset_num == 0) { nftset_rule_array[0] = &nftsets->ip; nftset_rule_enable_array[0] = &nftsets->ip_enable; nftset_rule_array[1] = &nftsets->ip6; nftset_rule_enable_array[1] = &nftsets->ip6_enable; nftset_num = 2; } if (strncmp(tok_set, "-", 2U) == 0) { continue; } family = strtok_r(tok_set, "#", &saveptr); if (family == NULL) { goto errout; } tablename = strtok_r(NULL, "#", &saveptr); if (tablename == NULL) { goto errout; } setname = strtok_r(NULL, "#", &saveptr); if (setname == NULL) { goto errout; } /* new nftset domain */ nftset = _dns_conf_get_nftable(family, tablename, setname); if (nftset == NULL) { goto errout; } for (int i = 0; i < nftset_num; i++) { nftset_rule_array[i]->familyname = nftset->nftfamilyname; nftset_rule_array[i]->nfttablename = nftset->nfttablename; nftset_rule_array[i]->nftsetname = nftset->nftsetname; *nftset_rule_enable_array[i] = 1; } nftset_num = 0; } ret = 0; goto clear; errout: ret = -1; clear: if (copied_name) { free(copied_name); } return ret; } int _config_nftset(void *data, int argc, char *argv[]) { char domain[DNS_MAX_CONF_CNAME_LEN]; char *value = argv[1]; int ret = 0; if (argc <= 1) { goto errout; } if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) { goto errout; } return _conf_domain_rule_nftset(domain, value); errout: tlog(TLOG_ERROR, "add nftset %s failed", value); return ret; } int _config_nftset_no_speed(void *data, int argc, char *argv[]) { char *nftsetname = argv[1]; if (argc <= 1) { goto errout; } if (_config_nftset_setvalue(&_config_current_rule_group()->ipset_nftset.nftset_no_speed, nftsetname) != 0) { goto errout; } return 0; errout: tlog(TLOG_ERROR, "add nftset %s failed", nftsetname); return -1; } ================================================ FILE: src/dns_conf/nftset.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_NFTSET_H_ #define _DNS_CONF_NFTSET_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ const struct dns_nftset_name *_dns_conf_get_nftable(const char *familyname, const char *tablename, const char *setname); void _config_nftset_table_destroy(void); int _config_nftset(void *data, int argc, char *argv[]); int _config_nftset_no_speed(void *data, int argc, char *argv[]); int _conf_domain_rule_nftset(char *domain, const char *nftsetname); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/plugin.c ================================================ /************************************************************************* * * 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 . */ #include "plugin.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include struct dns_conf_plugin_table dns_conf_plugin_table; static int _config_plugin_iter_free(void *data, const unsigned char *key, uint32_t key_len, void *value) { free(value); return 0; } static struct dns_conf_plugin *_config_get_plugin(const char *file) { uint32_t key = 0; struct dns_conf_plugin *plugin = NULL; key = hash_string(file); hash_for_each_possible(dns_conf_plugin_table.plugins, plugin, node, key) { if (strncmp(plugin->file, file, DNS_MAX_PATH) != 0) { continue; } return plugin; } return NULL; } const char *dns_conf_get_plugin_conf(const char *key) { if (key == NULL) { return NULL; } return art_search(&dns_conf_plugin_table.plugins_conf, (unsigned char *)key, strlen(key) + 1); } int _config_plugin(void *data, int argc, char *argv[]) { // clang-format off const char *plugin_dir [] = { #if UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFULL "/lib64/smartdns", "/usr/lib64/smartdns", "/usr/local/lib64/smartdns", "/lib64", "/usr/lib64", "/usr/local/lib64", #endif "/lib/smartdns", "/usr/lib/smartdns", "/usr/local/lib/smartdns", "/lib", "/usr/lib", "/usr/local/lib", }; // clang-format on const int plugin_dir_len = sizeof(plugin_dir) / sizeof(plugin_dir[0]); #ifdef BUILD_STATIC tlog(TLOG_ERROR, "plugin not support in static release, please install dynamic release."); goto errout; #endif char file[DNS_MAX_PATH]; unsigned int key = 0; int i = 0; char *ptr = NULL; char *ptr_end = NULL; if (argc < 1) { tlog(TLOG_ERROR, "invalid parameter."); goto errout; } if (argv[1] == NULL || argv[1][0] == '\0') { tlog(TLOG_ERROR, "plugin: invalid parameter."); goto errout; } file[0] = '\0'; if (strstr(argv[1], "/") == NULL) { /* relative path, search in plugin dir */ for (i = 0; i < plugin_dir_len; i++) { snprintf(file, sizeof(file), "%s/%s", plugin_dir[i], argv[1]); if (access(file, F_OK) == 0) { break; } } if (i == plugin_dir_len) { file[0] = '\0'; } } if (file[0] == '\0') { conf_get_conf_fullpath(argv[1], file, sizeof(file)); if (file[0] == '\0') { tlog(TLOG_ERROR, "plugin: invalid parameter."); goto errout; } if (access(file, F_OK) != 0) { tlog(TLOG_ERROR, "plugin '%s' not exists.", argv[1]); goto errout; } } struct dns_conf_plugin *plugin = _config_get_plugin(file); if (plugin != NULL) { tlog(TLOG_ERROR, "plugin '%s' already exists.", file); goto errout; } plugin = zalloc(1, sizeof(*plugin)); if (plugin == NULL) { goto errout; } safe_strncpy(plugin->file, file, sizeof(plugin->file) - 1); ptr = plugin->args; ptr_end = plugin->args + sizeof(plugin->args) - 2; for (i = 1; i < argc && ptr < ptr_end; i++) { safe_strncpy(ptr, argv[i], ptr_end - ptr - 1); ptr += strlen(argv[i]) + 1; } plugin->argc = argc - 1; plugin->args_len = ptr - plugin->args; key = hash_string(file); hash_add(dns_conf_plugin_table.plugins, &plugin->node, key); return 0; errout: return -1; } int _config_plugin_conf_add(const char *key, const char *value) { char *old_value = NULL; char *new_value = NULL; if (key == NULL || value == NULL) { goto errout; } if (key[0] == '\0' || value[0] == '\0') { goto errout; } new_value = strdup(value); if (new_value == NULL) { goto errout; } old_value = art_insert(&dns_conf_plugin_table.plugins_conf, (unsigned char *)key, strlen(key) + 1, new_value); if (old_value) { free(old_value); } return 0; errout: return -1; } void _config_plugin_table_init(void) { hash_init(dns_conf_plugin_table.plugins); art_tree_init(&dns_conf_plugin_table.plugins_conf); } void _config_plugin_table_destroy(void) { struct dns_conf_plugin *plugin = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(dns_conf_plugin_table.plugins, i, tmp, plugin, node) { hlist_del_init(&plugin->node); free(plugin); } } void _config_plugin_table_conf_destroy(void) { art_iter(&dns_conf_plugin_table.plugins_conf, _config_plugin_iter_free, NULL); art_tree_destroy(&dns_conf_plugin_table.plugins_conf); } void dns_conf_clear_all_plugin_conf(void) { _config_plugin_table_conf_destroy(); } ================================================ FILE: src/dns_conf/plugin.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_PLUGIN_H_ #define _DNS_CONF_PLUGIN_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_plugin(void *data, int argc, char *argv[]); int _config_plugin_conf_add(const char *key, const char *value); void _config_plugin_table_init(void); void _config_plugin_table_destroy(void); void dns_conf_clear_all_plugin_conf(void); void _config_plugin_table_conf_destroy(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/proxy_names.c ================================================ /************************************************************************* * * 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 . */ #include "proxy_names.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" struct dns_proxy_table dns_proxy_table; struct dns_proxy_names *dns_server_get_proxy_names(const char *proxyname) { uint32_t key = 0; struct dns_proxy_names *proxy = NULL; key = hash_string(proxyname); hash_for_each_possible(dns_proxy_table.proxy, proxy, node, key) { if (strncmp(proxy->proxy_name, proxyname, DNS_GROUP_NAME_LEN) == 0) { return proxy; } } return NULL; } /* create and get dns server group */ static struct dns_proxy_names *_dns_conf_get_proxy(const char *proxy_name) { uint32_t key = 0; struct dns_proxy_names *proxy = NULL; key = hash_string(proxy_name); hash_for_each_possible(dns_proxy_table.proxy, proxy, node, key) { if (strncmp(proxy->proxy_name, proxy_name, PROXY_NAME_LEN) == 0) { return proxy; } } proxy = zalloc(1, sizeof(*proxy)); if (proxy == NULL) { goto errout; } safe_strncpy(proxy->proxy_name, proxy_name, PROXY_NAME_LEN); hash_add(dns_proxy_table.proxy, &proxy->node, key); INIT_LIST_HEAD(&proxy->server_list); return proxy; errout: if (proxy) { free(proxy); } return NULL; } int _dns_conf_proxy_servers_add(const char *proxy_name, struct dns_proxy_servers *server) { struct dns_proxy_names *proxy = NULL; proxy = _dns_conf_get_proxy(proxy_name); if (proxy == NULL) { return -1; } list_add_tail(&server->list, &proxy->server_list); return 0; } const char *_dns_conf_get_proxy_name(const char *proxy_name) { struct dns_proxy_names *proxy = NULL; proxy = _dns_conf_get_proxy(proxy_name); if (proxy == NULL) { return NULL; } return proxy->proxy_name; } void _config_proxy_table_destroy(void) { struct dns_proxy_names *proxy = NULL; struct hlist_node *tmp = NULL; unsigned int i; struct dns_proxy_servers *server = NULL; struct dns_proxy_servers *server_tmp = NULL; hash_for_each_safe(dns_proxy_table.proxy, i, tmp, proxy, node) { hlist_del_init(&proxy->node); list_for_each_entry_safe(server, server_tmp, &proxy->server_list, list) { list_del(&server->list); free(server); } free(proxy); } } ================================================ FILE: src/dns_conf/proxy_names.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_PROXY_NAMES_H_ #define _DNS_CONF_PROXY_NAMES_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_conf_proxy_servers_add(const char *proxy_name, struct dns_proxy_servers *server); const char *_dns_conf_get_proxy_name(const char *proxy_name); void _config_proxy_table_destroy(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/proxy_server.c ================================================ /************************************************************************* * * 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 . */ #include "proxy_server.h" #include "proxy_names.h" #include "smartdns/util.h" #include int _config_proxy_server(void *data, int argc, char *argv[]) { char *servers_name = NULL; struct dns_proxy_servers *server = NULL; proxy_type_t type = PROXY_TYPE_END; char *ip = NULL; int opt = 0; int use_domain = 0; char scheme[DNS_MAX_CNAME_LEN] = {0}; int port = PORT_NOT_DEFINED; /* clang-format off */ static struct option long_options[] = { {"name", required_argument, NULL, 'n'}, {"use-domain", no_argument, NULL, 'd'}, {NULL, no_argument, NULL, 0} }; /* clang-format on */ if (argc <= 1) { return 0; } server = zalloc(1, sizeof(*server)); if (server == NULL) { tlog(TLOG_WARN, "malloc memory failed."); goto errout; } ip = argv[1]; if (parse_uri_ext(ip, scheme, server->username, server->password, server->server, &port, NULL) != 0) { goto errout; } /* process extra options */ optind = 1; while (1) { opt = getopt_long_only(argc, argv, "n:d", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'n': { servers_name = optarg; break; } case 'd': { use_domain = 1; break; } default: break; } } if (strcasecmp(scheme, "socks5") == 0) { if (port == PORT_NOT_DEFINED) { port = 1080; } type = PROXY_SOCKS5; } else if (strcasecmp(scheme, "http") == 0) { if (port == PORT_NOT_DEFINED) { port = 3128; } type = PROXY_HTTP; } else { tlog(TLOG_ERROR, "invalid scheme %s", scheme); return -1; } if (servers_name == NULL) { tlog(TLOG_ERROR, "please set name"); goto errout; } if (_dns_conf_proxy_servers_add(servers_name, server) != 0) { tlog(TLOG_ERROR, "add group failed."); goto errout; } /* add new server */ server->type = type; server->port = port; server->use_domain = use_domain; tlog(TLOG_DEBUG, "add proxy server %s", ip); return 0; errout: if (server) { free(server); } return -1; } ================================================ FILE: src/dns_conf/proxy_server.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_PROXY_SERVER_H_ #define _DNS_CONF_PROXY_SERVER_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_proxy_server(void *data, int argc, char *argv[]); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/ptr.c ================================================ /************************************************************************* * * 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 . */ #include "ptr.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include struct dns_ptr_table dns_ptr_table; static struct dns_ptr *_dns_conf_get_ptr(const char *ptr_domain) { uint32_t key = 0; struct dns_ptr *ptr = NULL; key = hash_string(ptr_domain); hash_for_each_possible(dns_ptr_table.ptr, ptr, node, key) { if (strncmp(ptr->ptr_domain, ptr_domain, DNS_MAX_PTR_LEN) != 0) { continue; } return ptr; } ptr = zalloc(1, sizeof(*ptr)); if (ptr == NULL) { goto errout; } safe_strncpy(ptr->ptr_domain, ptr_domain, DNS_MAX_PTR_LEN); hash_add(dns_ptr_table.ptr, &ptr->node, key); ptr->is_soa = 1; return ptr; errout: if (ptr) { free(ptr); } return NULL; } int _conf_ptr_add(const char *hostname, const char *ip, int is_dynamic) { struct dns_ptr *ptr = NULL; struct sockaddr_storage addr; unsigned char *paddr = NULL; socklen_t addr_len = sizeof(addr); char ptr_domain[DNS_MAX_PTR_LEN]; if (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) { goto errout; } switch (addr.ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&addr; paddr = (unsigned char *)&(addr_in->sin_addr.s_addr); snprintf(ptr_domain, sizeof(ptr_domain), "%d.%d.%d.%d.in-addr.arpa", paddr[3], paddr[2], paddr[1], paddr[0]); } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { paddr = addr_in6->sin6_addr.s6_addr + 12; snprintf(ptr_domain, sizeof(ptr_domain), "%d.%d.%d.%d.in-addr.arpa", paddr[3], paddr[2], paddr[1], paddr[0]); } else { paddr = addr_in6->sin6_addr.s6_addr; snprintf(ptr_domain, sizeof(ptr_domain), "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "%x.ip6.arpa", paddr[15] & 0xF, (paddr[15] >> 4) & 0xF, paddr[14] & 0xF, (paddr[14] >> 4) & 0xF, paddr[13] & 0xF, (paddr[13] >> 4) & 0xF, paddr[12] & 0xF, (paddr[12] >> 4) & 0xF, paddr[11] & 0xF, (paddr[11] >> 4) & 0xF, paddr[10] & 0xF, (paddr[10] >> 4) & 0xF, paddr[9] & 0xF, (paddr[9] >> 4) & 0xF, paddr[8] & 0xF, (paddr[8] >> 4) & 0xF, paddr[7] & 0xF, (paddr[7] >> 4) & 0xF, paddr[6] & 0xF, (paddr[6] >> 4) & 0xF, paddr[5] & 0xF, (paddr[5] >> 4) & 0xF, paddr[4] & 0xF, (paddr[4] >> 4) & 0xF, paddr[3] & 0xF, (paddr[3] >> 4) & 0xF, paddr[2] & 0xF, (paddr[2] >> 4) & 0xF, paddr[1] & 0xF, (paddr[1] >> 4) & 0xF, paddr[0] & 0xF, (paddr[0] >> 4) & 0xF); } } break; default: goto errout; break; } ptr = _dns_conf_get_ptr(ptr_domain); if (ptr == NULL) { goto errout; } if (is_dynamic == 1 && ptr->is_soa == 0 && ptr->is_dynamic == 0) { /* already set fix PTR, skip */ return 0; } ptr->is_dynamic = is_dynamic; ptr->is_soa = 0; safe_strncpy(ptr->hostname, hostname, DNS_MAX_CNAME_LEN); return 0; errout: return -1; } void _config_ptr_table_init(void) { hash_init(dns_ptr_table.ptr); } void _config_ptr_table_destroy(int only_dynamic) { struct dns_ptr *ptr = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(dns_ptr_table.ptr, i, tmp, ptr, node) { if (only_dynamic != 0 && ptr->is_dynamic == 0) { continue; } hlist_del_init(&ptr->node); free(ptr); } } ================================================ FILE: src/dns_conf/ptr.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_PTR_H_ #define _DNS_CONF_PTR_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _conf_ptr_add(const char *hostname, const char *ip, int is_dynamic); void _config_ptr_table_init(void); void _config_ptr_table_destroy(int only_dynamic); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/qtype_soa.c ================================================ /************************************************************************* * * 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 . */ #include "qtype_soa.h" #include "dns_conf_group.h" #include "smartdns/lib/stringutil.h" static int _conf_qtype_soa(uint8_t *soa_table, int argc, char *argv[]) { int i = 0; int j = 0; int is_clear = 0; if (argc <= 1) { return -1; } if (argc >= 2) { if (strncmp(argv[1], "-", sizeof("-")) == 0) { if (argc == 2) { memset(soa_table, 0, MAX_QTYPE_NUM / 8 + 1); return 0; } is_clear = 1; } if (strncmp(argv[1], "-,", sizeof(",")) == 0) { is_clear = 1; } } for (i = 1; i < argc; i++) { char sub_arg[1024]; safe_strncpy(sub_arg, argv[i], sizeof(sub_arg)); for (char *tok = strtok(sub_arg, ","); tok; tok = strtok(NULL, ",")) { char *dash = strstr(tok, "-"); if (dash != NULL) { *dash = '\0'; } if (*tok == '\0') { continue; } long start = atol(tok); long end = start; if (start > MAX_QTYPE_NUM || start < 0) { tlog(TLOG_ERROR, "invalid qtype %ld", start); continue; } if (dash != NULL && *(dash + 1) != '\0') { end = atol(dash + 1); if (end > MAX_QTYPE_NUM) { end = MAX_QTYPE_NUM; } } for (j = start; j <= end; j++) { int offset = j / 8; int bit = j % 8; if (is_clear) { soa_table[offset] &= ~(1 << bit); } else { soa_table[offset] |= (1 << bit); } } } } return 0; } int _config_qtype_soa(void *data, int argc, char *argv[]) { return _conf_qtype_soa(_config_current_rule_group()->soa_table, argc, argv); } ================================================ FILE: src/dns_conf/qtype_soa.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_QTYPE_SOA_H_ #define _DNS_CONF_QTYPE_SOA_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_qtype_soa(void *data, int argc, char *argv[]); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/server.c ================================================ /************************************************************************* * * 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 . */ #include "server.h" #include "client_subnet.h" #include "dns_conf_group.h" #include "proxy_names.h" #include "server_group.h" #include "smartdns/util.h" #include static int _config_server(int argc, char *argv[], dns_server_type_t type, int default_port) { int index = dns_conf.server_num; struct dns_servers *server = NULL; int port = -1; char *ip = NULL; char scheme[DNS_MAX_CNAME_LEN] = {0}; int opt = 0; int optind_last = 0; unsigned int result_flag = 0; unsigned int server_flag = 0; unsigned char *spki = NULL; int drop_packet_latency_ms = 0; int tcp_keepalive = -1; int is_bootstrap_dns = 0; char host_ip[DNS_MAX_IPLEN] = {0}; int no_tls_host_name = 0; int no_tls_host_verify = 0; const char *group_name = NULL; int ttl = 0; /* clang-format off */ static struct option long_options[] = { {"drop-packet-latency", required_argument, NULL, 'D'}, {"exclude-default-group", no_argument, NULL, 'e'}, /* exclude this from default group */ {"group", required_argument, NULL, 'g'}, /* add to group */ {"proxy", required_argument, NULL, 'p'}, /* proxy server */ {"no-check-certificate", no_argument, NULL, 'k'}, /* do not check certificate */ {"bootstrap-dns", no_argument, NULL, 'b'}, /* set as bootstrap dns */ {"interface", required_argument, NULL, 250}, /* interface */ #ifdef FEATURE_CHECK_EDNS /* experimental feature */ {"check-edns", no_argument, NULL, 251}, /* check edns */ #endif {"whitelist-ip", no_argument, NULL, 252}, /* filtering with whitelist-ip */ {"blacklist-ip", no_argument, NULL, 253}, /* filtering with blacklist-ip */ {"set-mark", required_argument, NULL, 254}, /* set mark */ {"subnet", required_argument, NULL, 256}, /* set subnet */ {"hitchhiking", no_argument, NULL, 257}, /* hitchhiking */ {"host-ip", required_argument, NULL, 258}, /* host ip */ {"spki-pin", required_argument, NULL, 259}, /* check SPKI pin */ {"host-name", required_argument, NULL, 260}, /* host name */ {"http-host", required_argument, NULL, 261}, /* http host */ {"tls-host-verify", required_argument, NULL, 262 }, /* verify tls hostname */ {"tcp-keepalive", required_argument, NULL, 263}, /* tcp keepalive */ {"subnet-all-query-types", no_argument, NULL, 264}, /* send subnent for all query types.*/ {"fallback", no_argument, NULL, 265}, /* fallback */ {"alpn", required_argument, NULL, 266}, /* alpn */ {NULL, no_argument, NULL, 0} }; /* clang-format on */ if (argc <= 1) { tlog(TLOG_ERROR, "invalid parameter."); return -1; } ip = argv[1]; if (index >= DNS_MAX_SERVERS) { tlog(TLOG_WARN, "exceeds max server number, %s", ip); return 0; } server = &dns_conf.servers[index]; server->spki[0] = '\0'; server->path[0] = '\0'; server->hostname[0] = '\0'; server->httphost[0] = '\0'; server->tls_host_verify[0] = '\0'; server->proxyname[0] = '\0'; server->set_mark = -1; server->drop_packet_latency_ms = drop_packet_latency_ms; server->tcp_keepalive = tcp_keepalive; server->subnet_all_query_types = 0; if (parse_uri(ip, scheme, server->server, &port, server->path) != 0) { return -1; } if (scheme[0] != '\0') { if (strcasecmp(scheme, "https") == 0) { type = DNS_SERVER_HTTPS; default_port = DEFAULT_DNS_HTTPS_PORT; } else if (strcasecmp(scheme, "http3") == 0) { type = DNS_SERVER_HTTP3; default_port = DEFAULT_DNS_HTTPS_PORT; } else if (strcasecmp(scheme, "h3") == 0) { type = DNS_SERVER_HTTP3; default_port = DEFAULT_DNS_HTTPS_PORT; } else if (strcasecmp(scheme, "quic") == 0) { type = DNS_SERVER_QUIC; default_port = DEFAULT_DNS_QUIC_PORT; } else if (strcasecmp(scheme, "tls") == 0) { type = DNS_SERVER_TLS; default_port = DEFAULT_DNS_TLS_PORT; } else if (strcasecmp(scheme, "tcp") == 0) { type = DNS_SERVER_TCP; default_port = DEFAULT_DNS_PORT; } else if (strcasecmp(scheme, "udp") == 0) { type = DNS_SERVER_UDP; default_port = DEFAULT_DNS_PORT; } else { tlog(TLOG_ERROR, "invalid scheme: %s", scheme); return -1; } } if (dns_is_quic_supported() == 0) { if (type == DNS_SERVER_QUIC || type == DNS_SERVER_HTTP3) { tlog(TLOG_ERROR, "QUIC/HTTP3 is not supported in this version."); tlog(TLOG_ERROR, "Please install the latest release with QUIC/HTTP3 support."); return -1; } } /* if port is not defined, set port to default 53 */ if (port == PORT_NOT_DEFINED) { port = default_port; } /* get current group */ if (_config_current_group()) { group_name = _config_current_group()->group_name; } /* if server is defined in a group, exclude from default group */ if (group_name && group_name[0] != '\0') { server_flag |= SERVER_FLAG_EXCLUDE_DEFAULT; } /* process extra options */ optind = 1; optind_last = 1; while (1) { opt = getopt_long_only(argc, argv, "D:kg:p:eb", long_options, NULL); if (opt == -1) { break; } switch (opt) { case 'D': { drop_packet_latency_ms = atoi(optarg); break; } case 'e': { server_flag |= SERVER_FLAG_EXCLUDE_DEFAULT; break; } case 'g': { /* first group, add later */ if (group_name == NULL) { group_name = optarg; break; } if (_dns_conf_get_group_set(optarg, server) != 0) { tlog(TLOG_ERROR, "add group failed."); goto errout; } break; } case 'p': { if (_dns_conf_get_proxy_name(optarg) == NULL) { tlog(TLOG_ERROR, "add proxy server failed."); goto errout; } safe_strncpy(server->proxyname, optarg, PROXY_NAME_LEN); break; } case 'k': { server->skip_check_cert = 1; no_tls_host_verify = 1; break; } case 'b': { is_bootstrap_dns = 1; break; } case 250: { safe_strncpy(server->ifname, optarg, MAX_INTERFACE_LEN); break; } case 251: { result_flag |= DNSSERVER_FLAG_CHECK_EDNS; break; } case 252: { result_flag |= DNSSERVER_FLAG_WHITELIST_IP; break; } case 253: { result_flag |= DNSSERVER_FLAG_BLACKLIST_IP; break; } case 254: { server->set_mark = atoll(optarg); break; } case 256: { _conf_client_subnet(optarg, &server->ipv4_ecs, &server->ipv6_ecs); break; } case 257: { server_flag |= SERVER_FLAG_HITCHHIKING; break; } case 258: { if (check_is_ipaddr(optarg) != 0) { goto errout; } safe_strncpy(host_ip, optarg, DNS_MAX_IPLEN); break; } case 259: { safe_strncpy(server->spki, optarg, DNS_MAX_SPKI_LEN); break; } case 260: { safe_strncpy(server->hostname, optarg, DNS_MAX_CNAME_LEN); if (strncmp(server->hostname, "-", 2) == 0) { server->hostname[0] = '\0'; no_tls_host_name = 1; } break; } case 261: { safe_strncpy(server->httphost, optarg, DNS_MAX_CNAME_LEN); break; } case 262: { safe_strncpy(server->tls_host_verify, optarg, DNS_MAX_CNAME_LEN); if (strncmp(server->tls_host_verify, "-", 2) == 0) { server->tls_host_verify[0] = '\0'; no_tls_host_verify = 1; } break; } case 263: { server->tcp_keepalive = atoi(optarg); break; } case 264: { server->subnet_all_query_types = 1; break; } case 265: { server->fallback = 1; break; } case 266: { safe_strncpy(server->alpn, optarg, DNS_MAX_ALPN_LEN); break; } default: if (optind > optind_last) { tlog(TLOG_WARN, "unknown server option: %s at '%s:%d'.", argv[optind - 1], conf_get_conf_file(), conf_get_current_lineno()); } break; } optind_last = optind; } if (check_is_ipaddr(server->server) != 0) { /* if server is domain name, then verify domain */ if (server->tls_host_verify[0] == '\0' && no_tls_host_verify == 0) { safe_strncpy(server->tls_host_verify, server->server, DNS_MAX_CNAME_LEN); } if (server->hostname[0] == '\0' && no_tls_host_name == 0) { safe_strncpy(server->hostname, server->server, DNS_MAX_CNAME_LEN); } if (server->httphost[0] == '\0') { safe_strncpy(server->httphost, server->server, DNS_MAX_CNAME_LEN); } if (host_ip[0] != '\0') { safe_strncpy(server->server, host_ip, DNS_MAX_IPLEN); } } /* if server is domain name, then verify domain */ if (server->tls_host_verify[0] == '\0' && server->hostname[0] != '\0' && no_tls_host_verify == 0) { safe_strncpy(server->tls_host_verify, server->hostname, DNS_MAX_CNAME_LEN); } /* add new server */ server->type = type; server->port = port; server->result_flag = result_flag; server->server_flag = server_flag; server->ttl = ttl; server->drop_packet_latency_ms = drop_packet_latency_ms; if (server->type == DNS_SERVER_HTTPS || server->type == DNS_SERVER_HTTP3) { if (server->path[0] == 0) { safe_strncpy(server->path, "/", sizeof(server->path)); } if (server->httphost[0] == '\0') { set_http_host(server->server, server->port, DEFAULT_DNS_HTTPS_PORT, server->httphost); } } if (group_name) { if (_dns_conf_get_group_set(group_name, server) != 0) { tlog(TLOG_ERROR, "add group failed."); goto errout; } } dns_conf.server_num++; tlog(TLOG_DEBUG, "add server %s, flag: %X, ttl: %d", ip, result_flag, ttl); if (is_bootstrap_dns) { server->server_flag |= SERVER_FLAG_EXCLUDE_DEFAULT; _dns_conf_get_group_set("bootstrap-dns", server); dns_conf_exist_bootstrap_dns = 1; } return 0; errout: if (spki) { free(spki); } return -1; } int _config_server_udp(void *data, int argc, char *argv[]) { return _config_server(argc, argv, DNS_SERVER_UDP, DEFAULT_DNS_PORT); } int _config_server_tcp(void *data, int argc, char *argv[]) { return _config_server(argc, argv, DNS_SERVER_TCP, DEFAULT_DNS_PORT); } int _config_server_tls(void *data, int argc, char *argv[]) { return _config_server(argc, argv, DNS_SERVER_TLS, DEFAULT_DNS_TLS_PORT); } int _config_server_https(void *data, int argc, char *argv[]) { int ret = 0; ret = _config_server(argc, argv, DNS_SERVER_HTTPS, DEFAULT_DNS_HTTPS_PORT); return ret; } int _config_server_quic(void *data, int argc, char *argv[]) { int ret = 0; ret = _config_server(argc, argv, DNS_SERVER_QUIC, DEFAULT_DNS_QUIC_PORT); return ret; } int _config_server_http3(void *data, int argc, char *argv[]) { int ret = 0; ret = _config_server(argc, argv, DNS_SERVER_HTTP3, DEFAULT_DNS_HTTPS_PORT); return ret; } ================================================ FILE: src/dns_conf/server.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_SERVER_H_ #define _DNS_CONF_SERVER_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_server_udp(void *data, int argc, char *argv[]); int _config_server_tcp(void *data, int argc, char *argv[]); int _config_server_tls(void *data, int argc, char *argv[]); int _config_server_https(void *data, int argc, char *argv[]); int _config_server_quic(void *data, int argc, char *argv[]); int _config_server_http3(void *data, int argc, char *argv[]); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/server_group.c ================================================ /************************************************************************* * * 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 . */ #include "server_group.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" /* dns groups */ struct dns_group_table dns_group_table; struct dns_server_groups *_dns_conf_get_group(const char *group_name) { uint32_t key = 0; struct dns_server_groups *group = NULL; key = hash_string(group_name); hash_for_each_possible(dns_group_table.group, group, node, key) { if (strncmp(group->group_name, group_name, DNS_GROUP_NAME_LEN) == 0) { return group; } } group = zalloc(1, sizeof(*group)); if (group == NULL) { goto errout; } safe_strncpy(group->group_name, group_name, DNS_GROUP_NAME_LEN); hash_add(dns_group_table.group, &group->node, key); return group; errout: if (group) { free(group); } return NULL; } int _dns_conf_get_group_set(const char *group_name, struct dns_servers *server) { struct dns_server_groups *group = NULL; int i = 0; group = _dns_conf_get_group(group_name); if (group == NULL) { return -1; } for (i = 0; i < group->server_num; i++) { if (group->servers[i] == server) { /* already in group */ return 0; } } if (group->server_num >= DNS_MAX_SERVERS) { return -1; } group->servers[group->server_num] = server; group->server_num++; return 0; } const char *_dns_conf_get_group_name(const char *group_name) { struct dns_server_groups *group = NULL; group = _dns_conf_get_group(group_name); if (group == NULL) { return NULL; } return group->group_name; } void _config_group_table_init(void) { hash_init(dns_group_table.group); } void _config_group_table_destroy(void) { struct dns_server_groups *group = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(dns_group_table.group, i, tmp, group, node) { hlist_del_init(&group->node); free(group); } } ================================================ FILE: src/dns_conf/server_group.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_SERVER_GROUP_H_ #define _DNS_CONF_SERVER_GROUP_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_conf_get_group_set(const char *group_name, struct dns_servers *server); struct dns_server_groups *_dns_conf_get_group(const char *group_name); const char *_dns_conf_get_group_name(const char *group_name); struct dns_conf_group *_config_rule_group_get(const char *group_name); struct dns_conf_group *dns_server_get_rule_group(const char *group_name); struct dns_conf_group *dns_server_get_default_rule_group(void); struct dns_conf_group *_config_rule_group_new(const char *group_name); void _config_group_table_init(void); void _config_group_table_destroy(void); void _config_rule_group_destroy(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/set_file.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "set_file.h" #include "smartdns/lib/idna.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include #include #include #include #include #include int _config_set_rule_each_from_list(const char *file, set_rule_add_func callback, void *priv) { FILE *fp = NULL; char line[MAX_LINE_LEN]; char value[DNS_MAX_CNAME_LEN]; int ret = 0; int line_no = 0; int filed_num = 0; fp = fopen(file, "r"); if (fp == NULL) { tlog(TLOG_ERROR, "open file %s error, %s", file, strerror(errno)); return -1; } line_no = 0; while (fgets(line, MAX_LINE_LEN, fp)) { char *p = line; line_no++; /* skip UTF-8 BOM */ if (line_no == 1 && (unsigned char)line[0] == 0xEF && (unsigned char)line[1] == 0xBB && (unsigned char)line[2] == 0xBF) { p += 3; } filed_num = sscanf(p, "%255s", value); if (filed_num <= 0) { continue; } if (value[0] == '#' || value[0] == '\n') { continue; } /* Normalize domain */ char domain[DNS_MAX_CNAME_LEN]; char *d_ptr = value; /* remove prefix . */ while (*d_ptr == '.') { if (*(d_ptr + 1) == '\0') { break; } d_ptr++; } if (utf8_to_punycode(d_ptr, strlen(d_ptr), domain, sizeof(domain)) <= 0) { tlog(TLOG_WARN, "process file %s failed at line %d, invalid domain %s.", file, line_no, d_ptr); continue; } ret = callback(domain, priv); if (ret != 0) { tlog(TLOG_WARN, "process file %s failed at line %d.", file, line_no); continue; } } fclose(fp); return ret; } int _config_foreach_file(const char *file_pattern, int (*callback)(const char *file, void *priv), void *priv) { char file_path[PATH_MAX]; char file_path_dir[PATH_MAX]; glob_t globbuf = {0}; if (file_pattern == NULL) { return -1; } if (file_pattern[0] != '/') { safe_strncpy(file_path_dir, conf_get_conf_file(), DNS_MAX_PATH); dir_name(file_path_dir); if (strncmp(file_path_dir, conf_get_conf_file(), sizeof(file_path_dir)) == 0) { if (snprintf(file_path, DNS_MAX_PATH, "%s", file_pattern) < 0) { return -1; } } else { if (snprintf(file_path, DNS_MAX_PATH, "%s/%s", file_path_dir, file_pattern) < 0) { return -1; } } } else { safe_strncpy(file_path, file_pattern, DNS_MAX_PATH); } errno = 0; if (glob(file_path, 0, NULL, &globbuf) != 0) { if (errno == 0) { return 0; } tlog(TLOG_ERROR, "open config file '%s' failed, %s", file_path, strerror(errno)); return -1; } for (size_t i = 0; i != globbuf.gl_pathc; ++i) { const char *file = globbuf.gl_pathv[i]; struct stat statbuf; if (stat(file, &statbuf) != 0) { continue; } if (!S_ISREG(statbuf.st_mode)) { continue; } if (callback(file, priv) != 0) { tlog(TLOG_ERROR, "load config file '%s' failed.", file); globfree(&globbuf); return -1; } } globfree(&globbuf); return 0; } ================================================ FILE: src/dns_conf/set_file.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_SET_FILE_H_ #define _DNS_CONF_SET_FILE_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _get_domain(char *value, char *domain, int max_domain_size, char **ptr_after_domain); int _config_foreach_file(const char *file_pattern, int (*callback)(const char *file, void *priv), void *priv); typedef int (*set_rule_add_func)(const char *value, void *priv); int _config_set_rule_each_from_list(const char *file, set_rule_add_func callback, void *priv); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/smartdns_domain.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns_domain.h" #include "domain_rule.h" #include void _config_setup_smartdns_domain(void) { char hostname[DNS_MAX_CNAME_LEN]; char domainname[DNS_MAX_CNAME_LEN]; hostname[0] = '\0'; domainname[0] = '\0'; /* get local domain name */ if (getdomainname(domainname, DNS_MAX_CNAME_LEN - 1) == 0) { /* check domain is valid */ if (strncmp(domainname, "(none)", DNS_MAX_CNAME_LEN - 1) == 0) { domainname[0] = '\0'; } } if (gethostname(hostname, DNS_MAX_CNAME_LEN - 1) == 0) { /* check hostname is valid */ if (strncmp(hostname, "(none)", DNS_MAX_CNAME_LEN - 1) == 0) { hostname[0] = '\0'; } } if (dns_conf.resolv_hostname == 1) { /* add hostname to rule table */ if (hostname[0] != '\0') { _config_domain_rule_flag_set(hostname, DOMAIN_FLAG_SMARTDNS_DOMAIN, 0); } /* add domainname to rule table */ if (domainname[0] != '\0') { char full_domain[DNS_MAX_CNAME_LEN]; snprintf(full_domain, DNS_MAX_CNAME_LEN, "%.64s.%.128s", hostname, domainname); _config_domain_rule_flag_set(full_domain, DOMAIN_FLAG_SMARTDNS_DOMAIN, 0); } } /* add server name to rule table */ if (dns_conf.server_name[0] != '\0' && strncmp(dns_conf.server_name, "smartdns", DNS_MAX_SERVER_NAME_LEN - 1) != 0) { _config_domain_rule_flag_set(dns_conf.server_name, DOMAIN_FLAG_SMARTDNS_DOMAIN, 0); } _config_domain_rule_flag_set("smartdns", DOMAIN_FLAG_SMARTDNS_DOMAIN, 0); } ================================================ FILE: src/dns_conf/smartdns_domain.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_SMARTDNS_DOMAIN_H_ #define _DNS_CONF_SMARTDNS_DOMAIN_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _config_setup_smartdns_domain(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/speed_check_mode.c ================================================ /************************************************************************* * * 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 . */ #include "speed_check_mode.h" #include "dns_conf_group.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include #include static int dns_has_cap_ping = 0; int dns_has_raw_cap = 0; int dns_ping_cap_force_enable = 0; static void _config_speed_check_mode_clear(struct dns_domain_check_orders *check_orders) { memset(check_orders->orders, 0, sizeof(check_orders->orders)); } int _config_speed_check_mode_parser(struct dns_domain_check_orders *check_orders, const char *mode) { char tmpbuff[DNS_MAX_OPT_LEN]; char *field = NULL; char *ptr = NULL; int order = 0; int port = 80; int i = 0; safe_strncpy(tmpbuff, mode, DNS_MAX_OPT_LEN); _config_speed_check_mode_clear(check_orders); ptr = tmpbuff; do { field = ptr; if (field == NULL || order >= DOMAIN_CHECK_NUM) { return 0; } ptr = strstr(ptr, ","); if (ptr) { *ptr = 0; } if (strncmp(field, "ping", sizeof("ping")) == 0) { if (dns_has_cap_ping == 0) { if (ptr) { ptr++; } continue; } check_orders->orders[order].type = DOMAIN_CHECK_ICMP; check_orders->orders[order].tcp_port = 0; dns_conf.has_icmp_check = 1; } else if (strstr(field, "tcp-syn") == field) { char *port_str = strstr(field, ":"); if (port_str) { port = atoi(port_str + 1); if (port <= 0 || port >= 65535) { port = 80; } } check_orders->orders[order].type = DOMAIN_CHECK_TCP_SYN; check_orders->orders[order].tcp_port = port; } else if (strstr(field, "tcp") == field) { char *port_str = strstr(field, ":"); if (port_str) { port = atoi(port_str + 1); if (port <= 0 || port >= 65535) { port = 80; } } check_orders->orders[order].type = DOMAIN_CHECK_TCP; check_orders->orders[order].tcp_port = port; dns_conf.has_tcp_check = 1; } else if (strncmp(field, "none", sizeof("none")) == 0) { for (i = order; i < DOMAIN_CHECK_NUM; i++) { check_orders->orders[i].type = DOMAIN_CHECK_NONE; check_orders->orders[i].tcp_port = 0; } return 0; } order++; if (ptr) { ptr++; } } while (ptr); return 0; } int _config_speed_check_mode(void *data, int argc, char *argv[]) { char mode[DNS_MAX_OPT_LEN]; if (argc <= 1) { return -1; } safe_strncpy(mode, argv[1], sizeof(mode)); return _config_speed_check_mode_parser(&_config_current_rule_group()->check_orders, mode); } int _dns_conf_speed_check_mode_verify(void) { struct dns_conf_group *group; struct hlist_node *tmp = NULL; unsigned long k = 0; int i = 0; int j = 0; int print_log = 0; int trim_tail = 0; int tcp_sync_fallback = 0; hash_for_each_safe(dns_conf_rule.group, k, tmp, group, node) { struct dns_domain_check_orders *check_orders = &group->check_orders; for (i = 0; i < DOMAIN_CHECK_NUM; i++) { if (check_orders->orders[i].type == DOMAIN_CHECK_ICMP) { if (dns_has_cap_ping == 0) { trim_tail = 1; } dns_conf.has_icmp_check = 1; } if (check_orders->orders[i].type == DOMAIN_CHECK_TCP) { dns_conf.has_tcp_check = 1; } if (dns_has_raw_cap == 0) { if (check_orders->orders[i].type == DOMAIN_CHECK_TCP_SYN) { if (dns_has_raw_cap == 0) { tcp_sync_fallback = 1; check_orders->orders[i].type = DOMAIN_CHECK_TCP; } else { dns_conf.has_tcp_syn_check = 1; } } } if (trim_tail == 1) { for (j = i + 1; j < DOMAIN_CHECK_NUM; j++) { check_orders->orders[j - 1].type = check_orders->orders[j].type; check_orders->orders[j - 1].tcp_port = check_orders->orders[j].tcp_port; } check_orders->orders[j - 1].type = DOMAIN_CHECK_NONE; check_orders->orders[j - 1].tcp_port = 0; print_log = 1; } trim_tail = 0; } } if (print_log) { tlog(TLOG_WARN, "speed check by ping or tcp-syn is disabled because smartdns does not have network raw privileges"); } if (tcp_sync_fallback) { tlog(TLOG_WARN, "tcp-syn speed check fallback to tcp mode because smartdns does not have network raw privileges"); } return 0; } int _dns_ping_cap_check(void) { int has_ping = 0; int has_raw_cap = 0; has_raw_cap = has_network_raw_cap(); has_ping = has_unprivileged_ping(); if (has_ping == 0) { if (errno == EACCES && has_raw_cap == 0) { tlog(TLOG_WARN, "unprivileged ping is disabled, please enable by setting net.ipv4.ping_group_range"); } } if (has_ping == 1 || has_raw_cap == 1) { dns_has_cap_ping = 1; } if (dns_ping_cap_force_enable) { dns_has_cap_ping = 1; } dns_has_raw_cap = has_raw_cap; return 0; } ================================================ FILE: src/dns_conf/speed_check_mode.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_SPEED_CHECK_MODE_H_ #define _DNS_CONF_SPEED_CHECK_MODE_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_ping_cap_check(void); int _config_speed_check_mode(void *data, int argc, char *argv[]); int _dns_conf_speed_check_mode_verify(void); int _config_speed_check_mode_parser(struct dns_domain_check_orders *check_orders, const char *mode); extern int dns_has_raw_cap; #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_conf/srv_record.c ================================================ /************************************************************************* * * 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 . */ #include "srv_record.h" #include "set_file.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include "domain_rule.h" static int _confg_srv_record_add(const char *domain, const char *host, unsigned short priority, unsigned short weight, unsigned short port) { struct dns_srv_record_rule *srv_rule = NULL; struct dns_srv_record *srv_record = NULL; int is_new = 0; enum domain_rule type = DOMAIN_RULE_SRV; srv_rule = dns_conf_get_domain_rule(domain, type); if (srv_rule == NULL) { srv_rule = _new_dns_rule(type); if (srv_rule == NULL) { goto errout; } INIT_LIST_HEAD(&srv_rule->record_list); is_new = 1; } srv_record = zalloc(1, sizeof(*srv_record)); if (srv_record == NULL) { goto errout; } safe_strncpy(srv_record->host, host, DNS_MAX_CONF_CNAME_LEN); srv_record->priority = priority; srv_record->weight = weight; srv_record->port = port; list_add_tail(&srv_record->list, &srv_rule->record_list); if (is_new) { if (_config_domain_rule_add(domain, type, srv_rule) != 0) { goto errout; } _dns_rule_put(&srv_rule->head); srv_rule = NULL; } return 0; errout: if (is_new && srv_rule != NULL) { _dns_rule_put(&srv_rule->head); } if (srv_record && srv_record->list.next == NULL) { free(srv_record); } return -1; } int _config_srv_record(void *data, int argc, char *argv[]) { char *value = NULL; char domain[DNS_MAX_CONF_CNAME_LEN]; char buff[DNS_MAX_CONF_CNAME_LEN]; char *ptr = NULL; int ret = -1; char *host_s; char *priority_s; char *weight_s; char *port_s; unsigned short priority = 0; unsigned short weight = 0; unsigned short port = 1; if (argc < 2) { goto errout; } value = argv[1]; if (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) { goto errout; } safe_strncpy(buff, value, sizeof(buff)); host_s = strtok_r(buff, ",", &ptr); if (host_s == NULL) { host_s = ""; goto out; } port_s = strtok_r(NULL, ",", &ptr); if (port_s != NULL) { port = atoi(port_s); } priority_s = strtok_r(NULL, ",", &ptr); if (priority_s != NULL) { priority = atoi(priority_s); } weight_s = strtok_r(NULL, ",", &ptr); if (weight_s != NULL) { weight = atoi(weight_s); } out: ret = _confg_srv_record_add(domain, host_s, priority, weight, port); if (ret != 0) { goto errout; } return 0; errout: tlog(TLOG_ERROR, "add srv-record %s:%s failed", domain, value); return -1; } ================================================ FILE: src/dns_conf/srv_record.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_CONF_SRV_RECORD_H_ #define _DNS_CONF_SRV_RECORD_H_ #include "dns_conf.h" #include "smartdns/dns_conf.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _config_srv_record(void *data, int argc, char *argv[]); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_plugin.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/dns_plugin.h" #include "smartdns/dns_conf.h" #include "smartdns/lib/conf.h" #include "smartdns/lib/hashtable.h" #include "smartdns/lib/list.h" #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #include #include #include #include struct dns_plugin_ops { struct list_head list; struct smartdns_operations ops; }; #define DNS_PLUGIN_MAX_ARGS 32 struct dns_plugin { struct hlist_node node; char file[PATH_MAX]; char args[PATH_MAX]; int argc; char *argv[DNS_PLUGIN_MAX_ARGS]; void *handle; dns_plugin_init_func init_func; dns_plugin_exit_func exit_func; }; struct dns_plugins { struct list_head list; pthread_rwlock_t lock; DECLARE_HASHTABLE(plugin, 4); }; static struct dns_plugins plugins; static atomic_t is_plugin_init = ATOMIC_INIT(0); int smartdns_plugin_func_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) { struct dns_plugin_ops *chain = NULL; int ret = 0; if (unlikely(atomic_read(&is_plugin_init) == 0)) { return 0; } pthread_rwlock_rdlock(&plugins.lock); list_for_each_entry(chain, &plugins.list, list) { if (!chain->ops.server_recv) { continue; } ret = chain->ops.server_recv(packet, inpacket, inpacket_len, local, local_len, from, from_len); if (ret != 0) { pthread_rwlock_unlock(&plugins.lock); return ret; } } pthread_rwlock_unlock(&plugins.lock); return 0; } void smartdns_plugin_func_server_complete_request(struct dns_request *request) { struct dns_plugin_ops *chain = NULL; if (unlikely(atomic_read(&is_plugin_init) == 0)) { return; } pthread_rwlock_rdlock(&plugins.lock); list_for_each_entry(chain, &plugins.list, list) { if (!chain->ops.server_query_complete) { continue; } chain->ops.server_query_complete(request); } pthread_rwlock_unlock(&plugins.lock); return; } void smartdns_plugin_func_server_log_callback(smartdns_log_level level, const char *msg, int msg_len) { struct dns_plugin_ops *chain = NULL; if (unlikely(atomic_read(&is_plugin_init) == 0)) { return; } pthread_rwlock_rdlock(&plugins.lock); list_for_each_entry(chain, &plugins.list, list) { if (!chain->ops.server_log) { continue; } chain->ops.server_log(level, msg, msg_len); } pthread_rwlock_unlock(&plugins.lock); return; } void smartdns_plugin_func_server_audit_log_callback(const char *msg, int msg_len) { struct dns_plugin_ops *chain = NULL; if (unlikely(atomic_read(&is_plugin_init) == 0)) { return; } pthread_rwlock_rdlock(&plugins.lock); list_for_each_entry(chain, &plugins.list, list) { if (!chain->ops.server_audit_log) { continue; } chain->ops.server_audit_log(msg, msg_len); } pthread_rwlock_unlock(&plugins.lock); return; } int smartdns_operations_register(const struct smartdns_operations *operations) { struct dns_plugin_ops *chain = NULL; chain = (struct dns_plugin_ops *)malloc(sizeof(struct dns_plugin_ops)); if (!chain) { return -1; } memcpy(&chain->ops, operations, sizeof(struct smartdns_operations)); pthread_rwlock_wrlock(&plugins.lock); list_add_tail(&chain->list, &plugins.list); pthread_rwlock_unlock(&plugins.lock); return 0; } int smartdns_operations_unregister(const struct smartdns_operations *operations) { struct dns_plugin_ops *chain = NULL; struct dns_plugin_ops *tmp = NULL; pthread_rwlock_wrlock(&plugins.lock); list_for_each_entry_safe(chain, tmp, &plugins.list, list) { if (memcmp(&chain->ops, operations, sizeof(struct smartdns_operations)) == 0) { list_del(&chain->list); pthread_rwlock_unlock(&plugins.lock); free(chain); return 0; } } pthread_rwlock_unlock(&plugins.lock); return -1; } static struct dns_plugin *_dns_plugin_get(const char *plugin_file) { struct dns_plugin *plugin = NULL; unsigned int key = 0; key = hash_string(plugin_file); pthread_rwlock_rdlock(&plugins.lock); hash_for_each_possible(plugins.plugin, plugin, node, key) { if (strncmp(plugin->file, plugin_file, PATH_MAX - 1) == 0) { pthread_rwlock_unlock(&plugins.lock); return plugin; } } pthread_rwlock_unlock(&plugins.lock); return NULL; } static int _dns_plugin_load_library(struct dns_plugin *plugin) { void *handle = NULL; dns_plugin_api_version_func version_func = NULL; dns_plugin_init_func init_func = NULL; dns_plugin_exit_func exit_func = NULL; unsigned int api_version = 0; tlog(TLOG_DEBUG, "load plugin %s", plugin->file); handle = dlopen(plugin->file, RTLD_LAZY | RTLD_LOCAL); if (!handle) { tlog(TLOG_ERROR, "load plugin %s failed: %s", plugin->file, dlerror()); return -1; } version_func = (dns_plugin_api_version_func)dlsym(handle, DNS_PLUGIN_API_VERSION_FUNC); if (!version_func) { tlog(TLOG_ERROR, "plugin %s has no api version function, maybe an old version plugin, please download latest version.", plugin->file); goto errout; } init_func = (dns_plugin_init_func)dlsym(handle, DNS_PLUGIN_INIT_FUNC); if (!init_func) { tlog(TLOG_ERROR, "load plugin failed: %s", dlerror()); tlog(TLOG_ERROR, "%s is not a valid smartdns plugin, please check 'plugin' option.", plugin->file); goto errout; } exit_func = (dns_plugin_exit_func)dlsym(handle, DNS_PLUGIN_EXIT_FUNC); if (!exit_func) { tlog(TLOG_ERROR, "load plugin failed: %s", dlerror()); tlog(TLOG_ERROR, "%s not a valid smartdns plugin, please check 'plugin' option.", plugin->file); goto errout; } api_version = version_func(); if (SMARTDNS_PLUGIN_API_VERSION_MAJOR(api_version) != SMARTDNS_PLUGIN_API_VERSION_MAJOR(SMARTDNS_PLUGIN_API_VERSION)) { tlog(TLOG_ERROR, "plugin %s api version %u.%u not compatible with smartdns api version %u.%u, please download matching " "version.", plugin->file, SMARTDNS_PLUGIN_API_VERSION_MAJOR(api_version), SMARTDNS_PLUGIN_API_VERSION_MINOR(api_version), SMARTDNS_PLUGIN_API_VERSION_MAJOR(SMARTDNS_PLUGIN_API_VERSION), SMARTDNS_PLUGIN_API_VERSION_MINOR(SMARTDNS_PLUGIN_API_VERSION)); goto errout; } else if (SMARTDNS_PLUGIN_API_VERSION_MINOR(api_version) > SMARTDNS_PLUGIN_API_VERSION_MINOR(SMARTDNS_PLUGIN_API_VERSION)) { tlog(TLOG_ERROR, "plugin %s api version %u.%u is newer than smartdns api version %u.%u, please download matching version.", plugin->file, SMARTDNS_PLUGIN_API_VERSION_MAJOR(api_version), SMARTDNS_PLUGIN_API_VERSION_MINOR(api_version), SMARTDNS_PLUGIN_API_VERSION_MAJOR(SMARTDNS_PLUGIN_API_VERSION), SMARTDNS_PLUGIN_API_VERSION_MINOR(SMARTDNS_PLUGIN_API_VERSION)); goto errout; } conf_getopt_reset(); int ret = init_func(plugin); conf_getopt_reset(); if (ret != 0) { tlog(TLOG_ERROR, "init plugin %s failed", plugin->file); goto errout; } plugin->handle = handle; plugin->init_func = init_func; plugin->exit_func = exit_func; return 0; errout: if (handle) { dlclose(handle); } return -1; } static int _dns_plugin_unload_library(struct dns_plugin *plugin) { int ret = 0; if (plugin->exit_func) { ret = plugin->exit_func(plugin); if (ret != 0) { tlog(TLOG_ERROR, "exit plugin %s failed", plugin->file); } } if (plugin->handle) { dlclose(plugin->handle); plugin->handle = NULL; } return 0; } static struct dns_plugin *_dns_plugin_new(const char *plugin_file) { struct dns_plugin *plugin = NULL; plugin = _dns_plugin_get(plugin_file); if (plugin) { return NULL; } plugin = (struct dns_plugin *)zalloc(1, sizeof(struct dns_plugin)); if (!plugin) { return NULL; } strncpy(plugin->file, plugin_file, PATH_MAX - 1); return plugin; } static int _dns_plugin_remove(struct dns_plugin *plugin) { _dns_plugin_unload_library(plugin); pthread_rwlock_wrlock(&plugins.lock); hash_del(&plugin->node); pthread_rwlock_unlock(&plugins.lock); free(plugin); return 0; } int dns_plugin_get_argc(struct dns_plugin *plugin) { return plugin->argc; } const char **dns_plugin_get_argv(struct dns_plugin *plugin) { return (const char **)plugin->argv; } int dns_plugin_add(const char *plugin_file, int argc, const char *args, int args_len) { struct dns_plugin *plugin = NULL; const char *plugin_args = NULL; plugin = _dns_plugin_new(plugin_file); if (!plugin) { tlog(TLOG_ERROR, "add plugin %s failed", plugin_file); return -1; } memcpy(plugin->args, args, PATH_MAX - 1); plugin->argc = argc; plugin_args = plugin->args; for (int i = 0; i < argc && i < DNS_PLUGIN_MAX_ARGS; i++) { plugin->argv[i] = (char *)plugin_args; plugin_args += strlen(plugin_args) + 1; } if (_dns_plugin_load_library(plugin) != 0) { goto errout; } hash_add(plugins.plugin, &plugin->node, hash_string(plugin_file)); return 0; errout: if (plugin) { _dns_plugin_remove(plugin); } return -1; } int dns_plugin_remove(const char *plugin_file) { struct dns_plugin *plugin = NULL; plugin = _dns_plugin_get(plugin_file); if (plugin == NULL) { return 0; } return _dns_plugin_remove(plugin); } static int _dns_plugin_remove_all_ops(void) { struct dns_plugin_ops *chain = NULL; struct dns_plugin_ops *tmp = NULL; pthread_rwlock_wrlock(&plugins.lock); list_for_each_entry_safe(chain, tmp, &plugins.list, list) { list_del(&chain->list); free(chain); } pthread_rwlock_unlock(&plugins.lock); return 0; } static int _dns_plugin_remove_all(void) { struct dns_plugin *plugin = NULL; struct hlist_node *tmp = NULL; unsigned int key = 0; pthread_rwlock_wrlock(&plugins.lock); /* avoid hang */ while (!hash_empty(plugins.plugin)) { pthread_rwlock_unlock(&plugins.lock); hash_for_each_safe(plugins.plugin, key, tmp, plugin, node) { _dns_plugin_remove(plugin); break; } pthread_rwlock_wrlock(&plugins.lock); } pthread_rwlock_unlock(&plugins.lock); return -1; } int dns_server_plugin_init(void) { if (atomic_read(&is_plugin_init) == 1) { return 0; } hash_init(plugins.plugin); INIT_LIST_HEAD(&plugins.list); if (pthread_rwlock_init(&plugins.lock, NULL) != 0) { tlog(TLOG_ERROR, "init plugin rwlock failed."); return -1; } atomic_set(&is_plugin_init, 1); return 0; } void dns_server_plugin_exit(void) { if (atomic_read(&is_plugin_init) == 0) { return; } _dns_plugin_remove_all_ops(); _dns_plugin_remove_all(); pthread_rwlock_destroy(&plugins.lock); atomic_set(&is_plugin_init, 0); return; } void smartdns_plugin_log(smartdns_log_level level, const char *file, int line, const char *func, const char *msg) { tlog_ext((tlog_level)level, file, line, func, NULL, "%s", msg); } int smartdns_plugin_can_log(smartdns_log_level level) { return tlog_getlevel() <= (tlog_level)level; } void smartdns_plugin_log_setlevel(smartdns_log_level level) { tlog_setlevel((tlog_level)level); } int smartdns_plugin_log_getlevel(void) { return tlog_getlevel(); } int smartdns_plugin_is_audit_enabled(void) { return dns_conf.audit_enable; } const char *smartdns_plugin_get_config(const char *key) { return dns_conf_get_plugin_conf(key); } void smartdns_plugin_clear_all_config(void) { dns_conf_clear_all_plugin_conf(); } ================================================ FILE: src/dns_server/address.c ================================================ /************************************************************************* * * 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 . */ #include "address.h" #include "context.h" #include "dns_server.h" #include "ptr.h" #include "request.h" #include "rules.h" #include "speed_check.h" int _dns_server_is_adblock_ipv6(const unsigned char addr[16]) { int i = 0; for (i = 0; i < 15; i++) { if (addr[i]) { return -1; } } if (addr[15] == 0 || addr[15] == 1) { return 0; } return -1; } int _dns_server_address_generate_order(int orders[], int order_num, int max_order_count) { int i = 0; int j = 0; int k = 0; unsigned int seed = time(NULL); for (i = 0; i < order_num && i < max_order_count; i++) { orders[i] = i; } for (i = 0; i < order_num && max_order_count; i++) { k = rand_r(&seed) % order_num; j = rand_r(&seed) % order_num; if (j == k) { continue; } int temp = orders[j]; orders[j] = orders[k]; orders[k] = temp; } return 0; } int _dns_server_process_address(struct dns_request *request) { struct dns_rule_address_IPV4 *address_ipv4 = NULL; struct dns_rule_address_IPV6 *address_ipv6 = NULL; int orders[DNS_MAX_REPLY_IP_NUM]; if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_ADDR) == 0) { goto errout; } /* address /domain/ rule */ switch (request->qtype) { case DNS_T_A: if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] == NULL) { goto errout; } address_ipv4 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV4); if (address_ipv4 == NULL) { goto errout; } _dns_server_address_generate_order(orders, address_ipv4->addr_num, DNS_MAX_REPLY_IP_NUM); memcpy(request->ip_addr, address_ipv4->ipv4_addr[orders[0]], DNS_RR_A_LEN); for (int i = 1; i < address_ipv4->addr_num; i++) { int index = orders[i]; if (index >= address_ipv4->addr_num) { continue; } _dns_ip_address_check_add(request, request->cname, address_ipv4->ipv4_addr[index], DNS_T_A, 1, NULL); } break; case DNS_T_AAAA: if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] == NULL) { goto errout; } address_ipv6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV6); if (address_ipv6 == NULL) { goto errout; } _dns_server_address_generate_order(orders, address_ipv6->addr_num, DNS_MAX_REPLY_IP_NUM); memcpy(request->ip_addr, address_ipv6->ipv6_addr[orders[0]], DNS_RR_AAAA_LEN); for (int i = 1; i < address_ipv6->addr_num; i++) { int index = orders[i]; if (index >= address_ipv6->addr_num) { continue; } _dns_ip_address_check_add(request, request->cname, address_ipv6->ipv6_addr[index], DNS_T_AAAA, 1, NULL); } break; default: goto errout; break; } request->rcode = DNS_RC_NOERROR; request->ip_ttl = _dns_server_get_local_ttl(request); request->has_ip = 1; struct dns_server_post_context context; _dns_server_post_context_init(&context, request); context.do_reply = 1; context.do_audit = 1; context.do_ipset = 1; context.select_all_best_ip = 1; _dns_request_post(&context); return 0; errout: return -1; } int _dns_ip_address_check_add(struct dns_request *request, char *cname, unsigned char *addr, dns_type_t addr_type, int ping_time, struct dns_ip_address **out_addr_map) { uint32_t key = 0; struct dns_ip_address *addr_map = NULL; int addr_len = 0; if (ping_time == 0) { ping_time = -1; } if (addr_type == DNS_T_A) { addr_len = DNS_RR_A_LEN; } else if (addr_type == DNS_T_AAAA) { addr_len = DNS_RR_AAAA_LEN; } else { return -1; } /* store the ip address and the number of hits */ key = jhash(addr, addr_len, 0); key = jhash(&addr_type, sizeof(addr_type), key); pthread_mutex_lock(&request->ip_map_lock); hash_for_each_possible(request->ip_map, addr_map, node, key) { if (addr_map->addr_type != addr_type) { continue; } if (memcmp(addr_map->ip_addr, addr, addr_len) != 0) { continue; } addr_map->hitnum++; addr_map->recv_tick = get_tick_count(); pthread_mutex_unlock(&request->ip_map_lock); return -1; } atomic_inc(&request->ip_map_num); addr_map = zalloc(1, sizeof(*addr_map)); if (addr_map == NULL) { pthread_mutex_unlock(&request->ip_map_lock); tlog(TLOG_ERROR, "malloc addr map failed"); return -1; } addr_map->addr_type = addr_type; addr_map->hitnum = 1; addr_map->recv_tick = get_tick_count(); addr_map->ping_time = ping_time; memcpy(addr_map->ip_addr, addr, addr_len); if (request->conf->dns_force_no_cname == 0) { safe_strncpy(addr_map->cname, cname, DNS_MAX_CNAME_LEN); } hash_add(request->ip_map, &addr_map->node, key); pthread_mutex_unlock(&request->ip_map_lock); if (out_addr_map != NULL) { *out_addr_map = addr_map; } return 0; } void _dns_server_select_possible_ipaddress(struct dns_request *request) { int maxhit = 0; unsigned long bucket = 0; unsigned long max_recv_tick = 0; struct dns_ip_address *addr_map = NULL; struct dns_ip_address *maxhit_addr_map = NULL; struct dns_ip_address *last_recv_addr_map = NULL; struct dns_ip_address *selected_addr_map = NULL; struct hlist_node *tmp = NULL; if (atomic_read(&request->notified) > 0) { return; } if (request->no_select_possible_ip != 0) { return; } if (request->ping_time > 0) { return; } /* Return the most likely correct IP address */ /* Returns the IP with the most hits, or the last returned record is considered to be the most likely * correct. */ pthread_mutex_lock(&request->ip_map_lock); hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node) { if (addr_map->addr_type != request->qtype) { continue; } if (addr_map->recv_tick - request->send_tick > max_recv_tick) { max_recv_tick = addr_map->recv_tick - request->send_tick; last_recv_addr_map = addr_map; } if (addr_map->hitnum > maxhit) { maxhit = addr_map->hitnum; maxhit_addr_map = addr_map; } } pthread_mutex_unlock(&request->ip_map_lock); if (maxhit_addr_map && maxhit > 1) { selected_addr_map = maxhit_addr_map; } else if (last_recv_addr_map) { selected_addr_map = last_recv_addr_map; } if (selected_addr_map == NULL) { return; } tlog(TLOG_DEBUG, "select best ip address, %s", request->domain); switch (request->qtype) { case DNS_T_A: { memcpy(request->ip_addr, selected_addr_map->ip_addr, DNS_RR_A_LEN); tlog(TLOG_DEBUG, "possible result: %s, rcode: %d, hitnum: %d, %d.%d.%d.%d", request->domain, request->rcode, selected_addr_map->hitnum, request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3]); } break; case DNS_T_AAAA: { memcpy(request->ip_addr, selected_addr_map->ip_addr, DNS_RR_AAAA_LEN); tlog(TLOG_DEBUG, "possible result: %s, rcode: %d, hitnum: %d, " "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", request->domain, request->rcode, selected_addr_map->hitnum, request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3], request->ip_addr[4], request->ip_addr[5], request->ip_addr[6], request->ip_addr[7], request->ip_addr[8], request->ip_addr[9], request->ip_addr[10], request->ip_addr[11], request->ip_addr[12], request->ip_addr[13], request->ip_addr[14], request->ip_addr[15]); } break; default: break; } } struct dns_ip_address *_dns_ip_address_get(struct dns_request *request, unsigned char *addr, dns_type_t addr_type) { uint32_t key = 0; struct dns_ip_address *addr_map = NULL; struct dns_ip_address *addr_tmp = NULL; int addr_len = 0; if (addr_type == DNS_T_A) { addr_len = DNS_RR_A_LEN; } else if (addr_type == DNS_T_AAAA) { addr_len = DNS_RR_AAAA_LEN; } else { return NULL; } /* store the ip address and the number of hits */ key = jhash(addr, addr_len, 0); key = jhash(&addr_type, sizeof(addr_type), key); pthread_mutex_lock(&request->ip_map_lock); hash_for_each_possible(request->ip_map, addr_tmp, node, key) { if (addr_type != addr_tmp->addr_type) { continue; } if (memcmp(addr_tmp->ip_addr, addr, addr_len) != 0) { continue; } addr_map = addr_tmp; break; } pthread_mutex_unlock(&request->ip_map_lock); return addr_map; } ================================================ FILE: src/dns_server/address.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_ADDRESS_ #define _DNS_SERVER_ADDRESS_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_server_select_possible_ipaddress(struct dns_request *request); int _dns_ip_address_check_add(struct dns_request *request, char *cname, unsigned char *addr, dns_type_t addr_type, int ping_time, struct dns_ip_address **out_addr_map); struct dns_ip_address *_dns_ip_address_get(struct dns_request *request, unsigned char *addr, dns_type_t addr_type); int _dns_server_process_address(struct dns_request *request); int _dns_server_is_adblock_ipv6(const unsigned char addr[16]); int _dns_server_address_generate_order(int orders[], int order_num, int max_order_count); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/answer.c ================================================ /************************************************************************* * * 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 . */ #include "answer.h" #include "address.h" #include "dns_server.h" #include "ip_rule.h" #include "request.h" #include "rules.h" #include "soa.h" #include "speed_check.h" #include static int _dns_server_process_answer_A_IP(struct dns_request *request, char *cname, unsigned char addr[4], int ttl, unsigned int result_flag) { char ip[DNS_MAX_CNAME_LEN] = {0}; int ip_check_result = 0; unsigned char *paddrs[MAX_IP_NUM]; int paddr_num = 0; struct dns_iplist_ip_addresses *alias = NULL; paddrs[paddr_num] = addr; paddr_num = 1; /* ip rule check */ ip_check_result = _dns_server_process_ip_rule(request, addr, 4, DNS_T_A, result_flag, &alias); if (ip_check_result == 0) { /* match */ return -1; } else if (ip_check_result == -2 || ip_check_result == -3) { /* skip, nxdomain */ return ip_check_result; } int ret = _dns_server_process_ip_alias(request, alias, paddrs, &paddr_num, MAX_IP_NUM, DNS_RR_A_LEN); if (ret != 0) { return ret; } for (int i = 0; i < paddr_num; i++) { unsigned char *paddr = paddrs[i]; if (atomic_read(&request->ip_map_num) == 0) { request->has_ip = 1; request->ip_addr_type = DNS_T_A; memcpy(request->ip_addr, paddr, DNS_RR_A_LEN); request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); if (cname[0] != 0 && request->has_cname == 0 && request->conf->dns_force_no_cname == 0) { request->has_cname = 1; safe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN); } } else { if (ttl < request->ip_ttl) { request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); } } /* Ad blocking result */ if (paddr[0] == 0 || paddr[0] == 127) { /* If half of the servers return the same result, then ignore this address */ if (atomic_inc_return(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) { request->rcode = DNS_RC_NOERROR; return -1; } } /* add this ip to request */ if (_dns_ip_address_check_add(request, cname, paddr, DNS_T_A, 0, NULL) != 0) { /* skip result */ return -2; } snprintf(ip, sizeof(ip), "%d.%d.%d.%d", paddr[0], paddr[1], paddr[2], paddr[3]); /* start ping */ _dns_server_request_get(request); if (_dns_server_check_speed(request, ip) != 0) { _dns_server_request_release(request); } } return 0; } static int _dns_server_process_answer_AAAA_IP(struct dns_request *request, char *cname, unsigned char addr[16], int ttl, unsigned int result_flag) { char ip[DNS_MAX_CNAME_LEN] = {0}; int ip_check_result = 0; unsigned char *paddrs[MAX_IP_NUM]; struct dns_iplist_ip_addresses *alias = NULL; int paddr_num = 0; paddrs[paddr_num] = addr; paddr_num = 1; ip_check_result = _dns_server_process_ip_rule(request, addr, 16, DNS_T_AAAA, result_flag, &alias); if (ip_check_result == 0) { /* match */ return -1; } else if (ip_check_result == -2 || ip_check_result == -3) { /* skip, nxdomain */ return ip_check_result; } int ret = _dns_server_process_ip_alias(request, alias, paddrs, &paddr_num, MAX_IP_NUM, DNS_RR_AAAA_LEN); if (ret != 0) { return ret; } for (int i = 0; i < paddr_num; i++) { unsigned char *paddr = paddrs[i]; if (atomic_read(&request->ip_map_num) == 0) { request->has_ip = 1; request->ip_addr_type = DNS_T_AAAA; memcpy(request->ip_addr, paddr, DNS_RR_AAAA_LEN); request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); if (cname[0] != 0 && request->has_cname == 0 && request->conf->dns_force_no_cname == 0) { request->has_cname = 1; safe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN); } } else { if (ttl < request->ip_ttl) { request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); } } /* Ad blocking result */ if (_dns_server_is_adblock_ipv6(paddr) == 0) { /* If half of the servers return the same result, then ignore this address */ if (atomic_inc_return(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) { request->rcode = DNS_RC_NOERROR; return -1; } } /* add this ip to request */ if (_dns_ip_address_check_add(request, cname, paddr, DNS_T_AAAA, 0, NULL) != 0) { /* skip result */ return -2; } snprintf(ip, sizeof(ip), "[%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x]", paddr[0], paddr[1], paddr[2], paddr[3], paddr[4], paddr[5], paddr[6], paddr[7], paddr[8], paddr[9], paddr[10], paddr[11], paddr[12], paddr[13], paddr[14], paddr[15]); /* start ping */ _dns_server_request_get(request); if (_dns_server_check_speed(request, ip) != 0) { _dns_server_request_release(request); } } return 0; } static int _dns_server_process_answer_A(struct dns_rrs *rrs, struct dns_request *request, const char *domain, char *cname, unsigned int result_flag) { int ttl = 0; unsigned char addr[4]; char name[DNS_MAX_CNAME_LEN] = {0}; if (request->qtype != DNS_T_A) { return -1; } /* get A result */ dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr); tlog(TLOG_DEBUG, "domain: %s TTL: %d IP: %d.%d.%d.%d", name, ttl, addr[0], addr[1], addr[2], addr[3]); /* if domain is not match */ if (strncasecmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncasecmp(cname, name, DNS_MAX_CNAME_LEN) != 0) { return -1; } _dns_server_request_get(request); int ret = _dns_server_process_answer_A_IP(request, cname, addr, ttl, result_flag); _dns_server_request_release(request); return ret; } static int _dns_server_process_answer_AAAA(struct dns_rrs *rrs, struct dns_request *request, const char *domain, char *cname, unsigned int result_flag) { unsigned char addr[16]; char name[DNS_MAX_CNAME_LEN] = {0}; int ttl = 0; if (request->qtype != DNS_T_AAAA) { /* ignore non-matched query type */ return -1; } dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr); tlog(TLOG_DEBUG, "domain: %s TTL: %d IP: %.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", name, ttl, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]); /* if domain is not match */ if (strncmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncmp(cname, name, DNS_MAX_CNAME_LEN) != 0) { return -1; } _dns_server_request_get(request); int ret = _dns_server_process_answer_AAAA_IP(request, cname, addr, ttl, result_flag); _dns_server_request_release(request); return ret; } static int _dns_server_process_answer_HTTPS(struct dns_rrs *rrs, struct dns_request *request, const char *domain, char *cname, unsigned int result_flag) { int ttl = 0; int ret = -1; char name[DNS_MAX_CNAME_LEN] = {0}; char target[DNS_MAX_CNAME_LEN] = {0}; struct dns_svcparam *p = NULL; int priority = 0; struct dns_request_https *https_svcb; int no_ipv4 = 0; int no_ipv6 = 0; int no_ech = 0; struct dns_https_record_rule *https_record_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_HTTPS); if (https_record_rule) { no_ipv4 = https_record_rule->filter.no_ipv4hint; no_ipv6 = https_record_rule->filter.no_ipv6hint; no_ech = https_record_rule->filter.no_ech; } ret = dns_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN); if (ret != 0) { tlog(TLOG_WARN, "get HTTPS svcparm failed"); return -1; } https_svcb = zalloc(1, sizeof(*https_svcb)); if (https_svcb == NULL) { return -1; } INIT_LIST_HEAD(&https_svcb->list); list_add_tail(&https_svcb->list, &request->https_svcb_list); tlog(TLOG_DEBUG, "domain: %s HTTPS: %s TTL: %d priority: %d", name, target, ttl, priority); https_svcb->ttl = ttl; https_svcb->priority = priority; safe_strncpy(https_svcb->target, target, sizeof(https_svcb->target)); safe_strncpy(https_svcb->domain, name, sizeof(https_svcb->domain)); request->ip_ttl = ttl; _dns_server_request_get(request); for (; p; p = dns_svcparm_next(rrs, p)) { switch (p->key) { case DNS_HTTPS_T_MANDATORY: { } break; case DNS_HTTPS_T_ALPN: { memcpy(https_svcb->alpn, p->value, sizeof(https_svcb->alpn)); https_svcb->alpn_len = p->len; } break; case DNS_HTTPS_T_NO_DEFAULT_ALPN: { } break; case DNS_HTTPS_T_PORT: { int port = *(unsigned short *)(p->value); https_svcb->port = ntohs(port); } break; case DNS_HTTPS_T_IPV4HINT: { struct dns_rule_address_IPV4 *address_ipv4 = NULL; if (_dns_server_is_return_soa_qtype(request, DNS_T_A) || no_ipv4 == 1) { break; } if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_ADDR) == 0) { break; } address_ipv4 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV4); if (address_ipv4 != NULL) { memcpy(request->ip_addr, address_ipv4->ipv4_addr, DNS_RR_A_LEN); request->has_ip = 1; request->ip_addr_type = DNS_T_A; break; } for (int k = 0; k < p->len / 4; k++) { _dns_server_process_answer_A_IP(request, cname, p->value + k * 4, ttl, result_flag); } } break; case DNS_HTTPS_T_ECH: { if (no_ech == 1) { break; } if (p->len > sizeof(https_svcb->ech)) { tlog(TLOG_WARN, "ech too long"); break; } memcpy(https_svcb->ech, p->value, p->len); https_svcb->ech_len = p->len; } break; case DNS_HTTPS_T_IPV6HINT: { struct dns_rule_address_IPV6 *address_ipv6 = NULL; if (_dns_server_is_return_soa_qtype(request, DNS_T_AAAA) || no_ipv6 == 1) { break; } if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_ADDR) == 0) { break; } address_ipv6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV6); if (address_ipv6 != NULL) { memcpy(request->ip_addr, address_ipv6->ipv6_addr, DNS_RR_AAAA_LEN); request->has_ip = 1; request->ip_addr_type = DNS_T_AAAA; break; } for (int k = 0; k < p->len / 16; k++) { _dns_server_process_answer_AAAA_IP(request, cname, p->value + k * 16, ttl, result_flag); } } break; default: break; } } _dns_server_request_release(request); return 0; } int _dns_server_process_answer(struct dns_request *request, const char *domain, struct dns_packet *packet, unsigned int result_flag, int *need_passthrouh) { int ttl = 0; char name[DNS_MAX_CNAME_LEN] = {0}; char cname[DNS_MAX_CNAME_LEN] = {0}; int rr_count = 0; int i = 0; int j = 0; struct dns_rrs *rrs = NULL; int ret = 0; int is_skip = 0; int has_result = 0; int is_rcode_set = 0; if (packet->head.rcode != DNS_RC_NOERROR && packet->head.rcode != DNS_RC_NXDOMAIN) { if (request->rcode == DNS_RC_SERVFAIL) { request->rcode = packet->head.rcode; request->remote_server_fail = 1; } tlog(TLOG_DEBUG, "inquery failed, %s, rcode = %d, id = %d\n", domain, packet->head.rcode, packet->head.id); if (request->remote_server_fail == 0) { return DNS_CLIENT_ACTION_DROP; } return DNS_CLIENT_ACTION_UNDEFINE; } for (j = 1; j < DNS_RRS_OPT; j++) { rrs = dns_get_rrs_start(packet, j, &rr_count); for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { has_result = 1; switch (rrs->type) { case DNS_T_A: { ret = _dns_server_process_answer_A(rrs, request, domain, cname, result_flag); if (ret == -1) { break; } else if (ret == -2) { is_skip = 1; continue; } else if (ret == -3) { return -1; } request->rcode = packet->head.rcode; is_rcode_set = 1; } break; case DNS_T_AAAA: { ret = _dns_server_process_answer_AAAA(rrs, request, domain, cname, result_flag); if (ret == -1) { break; } else if (ret == -2) { is_skip = 1; continue; } else if (ret == -3) { return -1; } request->rcode = packet->head.rcode; is_rcode_set = 1; } break; case DNS_T_NS: { char nsname[DNS_MAX_CNAME_LEN]; dns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, nsname, DNS_MAX_CNAME_LEN); tlog(TLOG_DEBUG, "NS: %s ttl: %d nsname: %s\n", name, ttl, nsname); } break; case DNS_T_CNAME: { char domain_name[DNS_MAX_CNAME_LEN] = {0}; char domain_cname[DNS_MAX_CNAME_LEN] = {0}; dns_get_CNAME(rrs, domain_name, DNS_MAX_CNAME_LEN, &ttl, domain_cname, DNS_MAX_CNAME_LEN); if (strncasecmp(domain_name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 && strncasecmp(domain_name, cname, DNS_MAX_CNAME_LEN - 1) != 0) { continue; } safe_strncpy(cname, domain_cname, DNS_MAX_CNAME_LEN); request->ttl_cname = _dns_server_get_conf_ttl(request, ttl); tlog(TLOG_DEBUG, "name: %s ttl: %d cname: %s\n", domain_name, ttl, cname); } break; case DNS_T_HTTPS: { ret = _dns_server_process_answer_HTTPS(rrs, request, domain, cname, result_flag); if (ret == -1) { break; } else if (ret == -2) { is_skip = 1; continue; } request->rcode = packet->head.rcode; is_rcode_set = 1; } break; case DNS_T_SOA: { /* if DNS64 enabled, skip check SOA. */ if (_dns_server_is_dns64_request(request)) { if (request->has_ip) { _dns_server_request_complete(request); } break; } request->has_soa = 1; if (request->rcode != DNS_RC_NOERROR) { request->rcode = packet->head.rcode; is_rcode_set = 1; } dns_get_SOA(rrs, name, 128, &ttl, &request->soa); tlog(TLOG_DEBUG, "domain: %s, qtype: %d, SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, " "expire: " "%d, minimum: %d", domain, request->qtype, request->soa.mname, request->soa.rname, request->soa.serial, request->soa.refresh, request->soa.retry, request->soa.expire, request->soa.minimum); request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); int soa_num = atomic_inc_return(&request->soa_num); if ((soa_num >= ((int)ceilf((float)dns_server_alive_num() / 3) + 1) || soa_num > 4) && atomic_read(&request->ip_map_num) <= 0) { request->ip_ttl = ttl; _dns_server_request_complete(request); } } break; default: tlog(TLOG_DEBUG, "%s, qtype: %d, rrstype = %d", name, rrs->type, j); break; } } } request->remote_server_fail = 0; if (request->rcode == DNS_RC_SERVFAIL && is_skip == 0) { request->rcode = packet->head.rcode; } /* return NOERROR if all ips are skipped */ if (request->rcode == DNS_RC_SERVFAIL && has_result == 1 && is_skip == 1) { request->rcode = DNS_RC_NOERROR; } if (has_result == 0 && request->rcode == DNS_RC_NOERROR && packet->head.tc == 1 && request->has_ip == 0 && request->has_soa == 0) { tlog(TLOG_DEBUG, "result is truncated, %s qtype: %d, rcode: %d, id: %d, retry.", domain, request->qtype, packet->head.rcode, packet->head.id); return DNS_CLIENT_ACTION_RETRY; } if (is_rcode_set == 0 && has_result == 1 && is_skip == 0) { /* need retry for some server. */ return DNS_CLIENT_ACTION_MAY_RETRY; } return DNS_CLIENT_ACTION_OK; } ================================================ FILE: src/dns_server/answer.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_ANSWER_ #define _DNS_SERVER_ANSWER_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_process_answer(struct dns_request *request, const char *domain, struct dns_packet *packet, unsigned int result_flag, int *need_passthrouh); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/audit.c ================================================ /************************************************************************* * * 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 . */ #include "audit.h" #include "dns_server.h" #include "smartdns/dns_plugin.h" #include static tlog_log *dns_audit; void _dns_server_audit_log(struct dns_server_post_context *context) { char req_host[MAX_IP_LEN]; char req_result[1024] = {0}; char *ip_msg = req_result; char req_time[MAX_IP_LEN] = {0}; struct tlog_time tm; int i = 0; int j = 0; int rr_count = 0; struct dns_rrs *rrs = NULL; char name[DNS_MAX_CNAME_LEN] = {0}; int ttl = 0; int len = 0; int left_len = sizeof(req_result); int total_len = 0; int ip_num = 0; struct dns_request *request = context->request; int has_soa = request->has_soa; if (atomic_read(&request->notified) == 1) { request->query_time = get_tick_count() - request->send_tick; } if (dns_audit == NULL || !dns_conf.audit_enable || context->do_audit == 0) { return; } /* skip log prefetch request and dualstack selection request */ if (request->prefetch || request->dualstack_selection_query) { return; } for (j = 1; j < DNS_RRS_OPT && context->packet; j++) { rrs = dns_get_rrs_start(context->packet, j, &rr_count); for (i = 0; i < rr_count && rrs && left_len > 0; i++, rrs = dns_get_rrs_next(context->packet, rrs)) { switch (rrs->type) { case DNS_T_A: { unsigned char ipv4_addr[4]; if (dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv4_addr) != 0) { continue; } if (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 && strncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) { continue; } const char *fmt = "%d.%d.%d.%d"; if (ip_num > 0) { fmt = ", %d.%d.%d.%d"; } len = snprintf(ip_msg + total_len, left_len, fmt, ipv4_addr[0], ipv4_addr[1], ipv4_addr[2], ipv4_addr[3]); ip_num++; has_soa = 0; } break; case DNS_T_AAAA: { unsigned char ipv6_addr[16]; if (dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv6_addr) != 0) { continue; } if (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 && strncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) { continue; } const char *fmt = "%s"; if (ip_num > 0) { fmt = ", %s"; } req_host[0] = '\0'; inet_ntop(AF_INET6, ipv6_addr, req_host, sizeof(req_host)); len = snprintf(ip_msg + total_len, left_len, fmt, req_host); ip_num++; has_soa = 0; } break; case DNS_T_SOA: { if (ip_num == 0) { has_soa = 1; } } break; default: continue; } if (len < 0 || len >= left_len) { left_len = 0; break; } left_len -= len; total_len += len; } } if (has_soa && ip_num == 0) { if (!dns_conf.audit_log_SOA) { return; } if (request->dualstack_selection_force_soa) { snprintf(req_result, left_len, "dualstack soa"); } else { snprintf(req_result, left_len, "soa"); } } get_host_by_addr(req_host, sizeof(req_host), &request->addr); tlog_localtime(&tm); if (req_host[0] == '\0') { safe_strncpy(req_host, "API", MAX_IP_LEN); } if (dns_conf.audit_syslog == 0) { snprintf(req_time, sizeof(req_time), "[%.4d-%.2d-%.2d %.2d:%.2d:%.2d,%.3d] ", tm.year, tm.mon, tm.mday, tm.hour, tm.min, tm.sec, tm.usec / 1000); } tlog_printf(dns_audit, "%s%s query %s, type %d, time %dms, speed: %.1fms, group %s, result %s\n", req_time, req_host, request->domain, request->qtype, request->query_time, ((float)request->ping_time) / 10, request->dns_group_name[0] != '\0' ? request->dns_group_name : DNS_SERVER_GROUP_DEFAULT, req_result); } static int _dns_server_audit_syslog(struct tlog_log *log, const char *buff, int bufflen) { syslog(LOG_INFO, "%.*s", bufflen, buff); return 0; } static int _dns_server_audit_output_callback(struct tlog_log *log, const char *buff, int bufflen) { smartdns_plugin_func_server_audit_log_callback(buff, bufflen); if (dns_conf.audit_syslog) { return _dns_server_audit_syslog(log, buff, bufflen); } return tlog_write(log, buff, bufflen); } int _dns_server_audit_init(void) { char *audit_file = SMARTDNS_AUDIT_FILE; unsigned int tlog_flag = 0; if (dns_conf.audit_enable == 0) { return 0; } if (dns_conf.audit_file[0] != 0) { audit_file = dns_conf.audit_file; } if (dns_conf.audit_syslog) { tlog_flag |= TLOG_SEGMENT; } dns_audit = tlog_open(audit_file, dns_conf.audit_size, dns_conf.audit_num, 0, tlog_flag); if (dns_audit == NULL) { return -1; } tlog_reg_output_func(dns_audit, _dns_server_audit_output_callback); if (dns_conf.audit_file_mode > 0) { tlog_set_permission(dns_audit, dns_conf.audit_file_mode, dns_conf.audit_file_mode); } if (dns_conf.audit_console != 0) { tlog_logscreen(dns_audit, 1); } return 0; } ================================================ FILE: src/dns_server/audit.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_AUDIT_ #define _DNS_SERVER_AUDIT_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_server_audit_log(struct dns_server_post_context *context); int _dns_server_audit_init(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/cache.c ================================================ /************************************************************************* * * 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 . */ #include "cache.h" #include "answer.h" #include "context.h" #include "dns_server.h" #include "prefetch.h" #include "request.h" #include "rules.h" #include "soa.h" #include #include #include #include int _dns_server_expired_cache_ttl(struct dns_cache *cache, int serve_expired_ttl) { return cache->info.insert_time + cache->info.ttl + serve_expired_ttl - time(NULL); } static int _dns_cache_is_specify_packet(int qtype) { switch (qtype) { case DNS_T_PTR: case DNS_T_HTTPS: case DNS_T_TXT: case DNS_T_SRV: case DNS_T_CAA: break; default: return -1; break; } return 0; } static int _dns_server_get_cache_timeout(struct dns_request *request, struct dns_cache_key *cache_key, int ttl) { int timeout = 0; int prefetch_time = 0; int is_serve_expired = request->conf->dns_serve_expired; if (request->rcode != DNS_RC_NOERROR) { return ttl + 1; } if (request->is_mdns_lookup == 1) { return ttl + 1; } if (request->conf->dns_prefetch) { prefetch_time = 1; } if ((request->prefetch_flags & PREFETCH_FLAGS_NOPREFETCH)) { prefetch_time = 0; } if (request->edns0_do == 1) { prefetch_time = 0; } if (request->no_serve_expired) { is_serve_expired = 0; } if (prefetch_time == 1) { if (is_serve_expired) { timeout = request->conf->dns_serve_expired_prefetch_time; if (timeout == 0) { timeout = request->conf->dns_serve_expired_ttl / 2; if (timeout == 0 || timeout > EXPIRED_DOMAIN_PREFETCH_TIME) { timeout = EXPIRED_DOMAIN_PREFETCH_TIME; } } if ((request->prefetch_flags & PREFETCH_FLAGS_EXPIRED) == 0) { timeout += ttl; } else if (cache_key != NULL) { struct dns_cache *old_cache = dns_cache_lookup(cache_key); if (old_cache) { time_t next_ttl = _dns_server_expired_cache_ttl(old_cache, request->conf->dns_serve_expired_ttl) - old_cache->info.ttl + ttl; if (next_ttl < timeout) { timeout = next_ttl; } dns_cache_release(old_cache); } } } else { timeout = ttl - 3; } } else { timeout = ttl; if (is_serve_expired) { timeout += request->conf->dns_serve_expired_ttl; } timeout += 3; } if (timeout <= 0) { timeout = 1; } return timeout; } int _dns_server_request_update_cache(struct dns_request *request, int speed, dns_type_t qtype, struct dns_cache_data *cache_data, int cache_ttl) { int ttl = 0; int ret = -1; if (qtype != DNS_T_A && qtype != DNS_T_AAAA && qtype != DNS_T_HTTPS) { goto errout; } if (cache_ttl > 0) { ttl = cache_ttl; } else { ttl = _dns_server_get_conf_ttl(request, request->ip_ttl); } tlog(TLOG_DEBUG, "cache %s qtype: %d ttl: %d\n", request->domain, qtype, ttl); /* if doing prefetch, update cache only */ struct dns_cache_key cache_key; cache_key.dns_group_name = request->dns_group_name; cache_key.domain = request->domain; cache_key.qtype = request->qtype; cache_key.query_flag = request->server_flags; if (request->prefetch) { /* no prefetch for mdns request */ if (request->is_mdns_lookup) { ret = 0; goto errout; } if (dns_cache_replace(&cache_key, request->rcode, ttl, speed, _dns_server_get_cache_timeout(request, &cache_key, ttl), !(request->prefetch_flags & PREFETCH_FLAGS_EXPIRED), cache_data) != 0) { ret = 0; goto errout; } } else { /* insert result to cache */ if (dns_cache_insert(&cache_key, request->rcode, ttl, speed, _dns_server_get_cache_timeout(request, NULL, ttl), cache_data) != 0) { ret = -1; goto errout; } } return 0; errout: if (cache_data) { dns_cache_data_put(cache_data); } return ret; } int _dns_cache_cname_packet(struct dns_server_post_context *context) { struct dns_packet *packet = context->packet; struct dns_packet *cname_packet = NULL; int ret = -1; int i = 0; int j = 0; int rr_count = 0; int ttl = 0; int speed = 0; unsigned char packet_buff[DNS_PACKSIZE]; unsigned char inpacket_buff[DNS_IN_PACKSIZE]; int inpacket_len = 0; struct dns_cache_data *cache_packet = NULL; struct dns_rrs *rrs = NULL; char name[DNS_MAX_CNAME_LEN] = {0}; cname_packet = (struct dns_packet *)packet_buff; int has_result = 0; struct dns_request *request = context->request; if (request->has_cname == 0 || request->no_cache_cname == 1 || request->no_cache == 1) { return 0; } /* init a new DNS packet */ ret = dns_packet_init(cname_packet, DNS_PACKSIZE, &packet->head); if (ret != 0) { return -1; } /* add request domain */ ret = dns_add_domain(cname_packet, request->cname, context->qtype, DNS_C_IN); if (ret != 0) { return -1; } for (j = 1; j < DNS_RRS_OPT && context->packet; j++) { rrs = dns_get_rrs_start(context->packet, j, &rr_count); for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(context->packet, rrs)) { switch (rrs->type) { case DNS_T_A: { unsigned char ipv4_addr[4]; if (dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv4_addr) != 0) { continue; } if (strncasecmp(request->cname, name, DNS_MAX_CNAME_LEN - 1) != 0) { continue; } ret = dns_add_A(cname_packet, DNS_RRS_AN, request->cname, ttl, ipv4_addr); if (ret != 0) { return -1; } has_result = 1; } break; case DNS_T_AAAA: { unsigned char ipv6_addr[16]; if (dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv6_addr) != 0) { continue; } if (strncasecmp(request->cname, name, DNS_MAX_CNAME_LEN - 1) != 0) { continue; } ret = dns_add_AAAA(cname_packet, DNS_RRS_AN, request->cname, ttl, ipv6_addr); if (ret != 0) { return -1; } has_result = 1; } break; case DNS_T_SOA: { struct dns_soa soa; if (dns_get_SOA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, &soa) != 0) { continue; } ret = dns_add_SOA(cname_packet, DNS_RRS_AN, request->cname, ttl, &soa); if (ret != 0) { return -1; } has_result = 1; break; } default: continue; } } } if (has_result == 0) { return 0; } inpacket_len = dns_encode(inpacket_buff, DNS_IN_PACKSIZE, cname_packet); if (inpacket_len <= 0) { return -1; } if (context->qtype != DNS_T_A && context->qtype != DNS_T_AAAA) { return -1; } cache_packet = dns_cache_new_data_packet(inpacket_buff, inpacket_len); if (cache_packet == NULL) { goto errout; } ttl = _dns_server_get_conf_ttl(request, request->ip_ttl); speed = request->ping_time; tlog(TLOG_DEBUG, "Cache CNAME: %s, qtype: %d, speed: %d", request->cname, request->qtype, speed); /* if doing prefetch, update cache only */ struct dns_cache_key cache_key; cache_key.dns_group_name = request->dns_group_name; cache_key.domain = request->cname; cache_key.qtype = context->qtype; cache_key.query_flag = request->server_flags; if (request->prefetch) { if (dns_cache_replace(&cache_key, request->rcode, ttl, speed, _dns_server_get_cache_timeout(request, &cache_key, ttl), !(request->prefetch_flags & PREFETCH_FLAGS_EXPIRED), cache_packet) != 0) { ret = 0; goto errout; } } else { /* insert result to cache */ if (dns_cache_insert(&cache_key, request->rcode, ttl, speed, _dns_server_get_cache_timeout(request, NULL, ttl), cache_packet) != 0) { ret = -1; goto errout; } } return 0; errout: if (cache_packet) { dns_cache_data_put(cache_packet); } return ret; } int _dns_cache_packet(struct dns_server_post_context *context) { struct dns_request *request = context->request; int ret = -1; struct dns_cache_data *cache_packet = dns_cache_new_data_packet(context->inpacket, context->inpacket_len); if (cache_packet == NULL) { goto errout; } /* if doing prefetch, update cache only */ struct dns_cache_key cache_key; cache_key.dns_group_name = request->dns_group_name; cache_key.domain = request->domain; cache_key.qtype = context->qtype; cache_key.query_flag = request->server_flags; if (request->prefetch) { /* no prefetch for mdns request */ if (request->is_mdns_lookup) { ret = 0; goto errout; } if (dns_cache_replace(&cache_key, request->rcode, request->ip_ttl, -1, _dns_server_get_cache_timeout(request, &cache_key, request->ip_ttl), !(request->prefetch_flags & PREFETCH_FLAGS_EXPIRED), cache_packet) != 0) { ret = 0; goto errout; } } else { /* insert result to cache */ if (dns_cache_insert(&cache_key, request->rcode, request->ip_ttl, -1, _dns_server_get_cache_timeout(request, NULL, request->ip_ttl), cache_packet) != 0) { ret = -1; goto errout; } } return 0; errout: if (cache_packet) { dns_cache_data_put(cache_packet); } return ret; } int _dns_cache_specify_packet(struct dns_server_post_context *context) { if (_dns_cache_is_specify_packet(context->qtype) != 0) { return 0; } return _dns_cache_packet(context); } int _dns_cache_try_keep_old_cache(struct dns_request *request) { struct dns_cache_key cache_key; cache_key.dns_group_name = request->dns_group_name; cache_key.domain = request->domain; cache_key.qtype = request->qtype; cache_key.query_flag = request->server_flags; return dns_cache_update_timer(&cache_key, DNS_SERVER_TMOUT_TTL); } static int _dns_server_process_cache_packet(struct dns_request *request, struct dns_cache *dns_cache) { int ret = -1; struct dns_cache_packet *cache_packet = NULL; if (dns_cache->info.qtype != request->qtype) { goto out; } cache_packet = (struct dns_cache_packet *)dns_cache_get_data(dns_cache); if (cache_packet == NULL) { goto out; } int do_ipset = (dns_cache_get_ttl(dns_cache) == 0); if (dns_cache_is_visited(dns_cache) == 0) { do_ipset = 1; } struct dns_server_post_context context; _dns_server_post_context_init(&context, request); if (request->original_domain != NULL && cache_packet->head.size < DNS_IN_PACKSIZE) { context.inpacket = context.inpacket_buff; memcpy(context.inpacket, cache_packet->data, cache_packet->head.size); } else { context.inpacket = cache_packet->data; } context.inpacket_len = cache_packet->head.size; request->ping_time = dns_cache->info.speed; if (dns_decode(context.packet, context.packet_maxlen, cache_packet->data, cache_packet->head.size) != 0) { tlog(TLOG_ERROR, "decode cache failed, %d, %d", context.packet_maxlen, context.inpacket_len); goto out; } /* Check if records in cache contain DNSSEC, if not exist, skip cache */ if (request->passthrough == 1) { if ((dns_get_OPT_option(context.packet) & DNS_OPT_FLAG_DO) == 0 && request->edns0_do == 1) { goto out; } } request->is_cache_reply = 1; request->rcode = context.packet->head.rcode; context.do_cache = 0; context.do_ipset = do_ipset; context.do_audit = 1; context.do_reply = 1; context.is_cache_reply = 1; context.reply_ttl = _dns_server_get_expired_ttl_reply(request, dns_cache); ret = _dns_server_reply_passthrough(&context); out: if (cache_packet) { dns_cache_data_put((struct dns_cache_data *)cache_packet); } return ret; } static int _dns_server_process_cache_data(struct dns_request *request, struct dns_cache *dns_cache) { int ret = -1; request->ping_time = dns_cache->info.speed; ret = _dns_server_process_cache_packet(request, dns_cache); if (ret != 0) { goto out; } return 0; out: return -1; } int _dns_server_process_cache(struct dns_request *request) { struct dns_cache *dns_cache = NULL; struct dns_cache *dualstack_dns_cache = NULL; int ret = -1; if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_CACHE) == 0) { goto out; } struct dns_cache_key cache_key; cache_key.dns_group_name = request->dns_group_name; cache_key.domain = request->domain; cache_key.qtype = request->qtype; cache_key.query_flag = request->server_flags; dns_cache = dns_cache_lookup(&cache_key); if (dns_cache == NULL) { goto out; } if (request->qtype != dns_cache->info.qtype) { goto out; } if (request->qtype == DNS_T_A && request->conf->dns_dualstack_ip_allow_force_AAAA == 0) { goto reply_cache; } if (request->qtype != DNS_T_A && request->qtype != DNS_T_AAAA) { goto reply_cache; } if (request->dualstack_selection) { int dualstack_qtype = 0; if (request->qtype == DNS_T_A) { dualstack_qtype = DNS_T_AAAA; } else if (request->qtype == DNS_T_AAAA) { dualstack_qtype = DNS_T_A; } else { goto reply_cache; } if (_dns_server_is_dns64_request(request) == 1) { goto reply_cache; } cache_key.qtype = dualstack_qtype; dualstack_dns_cache = dns_cache_lookup(&cache_key); if (dualstack_dns_cache == NULL && request->cname[0] != '\0') { cache_key.domain = request->cname; dualstack_dns_cache = dns_cache_lookup(&cache_key); } if (dualstack_dns_cache && (dualstack_dns_cache->info.speed > 0)) { if ((dualstack_dns_cache->info.speed + (request->conf->dns_dualstack_ip_selection_threshold * 10)) < dns_cache->info.speed || dns_cache->info.speed < 0) { tlog(TLOG_DEBUG, "cache result: %s, qtype: %d, force %s preferred, id: %d, time1: %d, time2: %d", request->domain, request->qtype, request->qtype == DNS_T_AAAA ? "IPv4" : "IPv6", request->id, dns_cache->info.speed, dualstack_dns_cache->info.speed); request->ip_ttl = _dns_server_get_expired_ttl_reply(request, dualstack_dns_cache); ret = _dns_server_reply_SOA(DNS_RC_NOERROR, request); goto out_update_cache; } } } reply_cache: if (dns_cache_get_ttl(dns_cache) <= 0 && request->no_serve_expired == 1) { goto out; } ret = _dns_server_process_cache_data(request, dns_cache); if (ret != 0) { goto out; } out_update_cache: if (dns_cache_get_ttl(dns_cache) == 0) { struct dns_server_query_option dns_query_options; int prefetch_flags = 0; dns_query_options.server_flags = request->server_flags; dns_query_options.dns_group_name = request->dns_group_name; if (request->conn == NULL) { dns_query_options.server_flags = dns_cache_get_query_flag(dns_cache); dns_query_options.dns_group_name = dns_cache_get_dns_group_name(dns_cache); } dns_query_options.ecs_enable_flag = 0; if (request->has_ecs) { dns_query_options.ecs_enable_flag |= DNS_QUEY_OPTION_ECS_DNS; memcpy(&dns_query_options.ecs_dns, &request->ecs, sizeof(dns_query_options.ecs_dns)); } if (request->edns0_do) { dns_query_options.ecs_enable_flag |= DNS_QUEY_OPTION_EDNS0_DO; prefetch_flags |= PREFETCH_FLAGS_NOPREFETCH; } _dns_server_prefetch_request(request->domain, request->qtype, &dns_query_options, prefetch_flags); } else { dns_cache_update(dns_cache); } out: if (dns_cache) { dns_cache_release(dns_cache); } if (dualstack_dns_cache) { dns_cache_release(dualstack_dns_cache); dualstack_dns_cache = NULL; } return ret; } void _dns_server_save_cache_to_file(void) { time_t now; int check_time = dns_conf.cache_checkpoint_time; if (dns_conf.cache_persist == 0 || dns_conf.cachesize <= 0 || dns_conf.cache_checkpoint_time <= 0) { return; } time(&now); if (server.cache_save_pid > 0) { int ret = waitpid(server.cache_save_pid, NULL, WNOHANG); if (ret == server.cache_save_pid) { server.cache_save_pid = 0; } else if (ret < 0) { tlog(TLOG_ERROR, "waitpid failed, errno %d, error info '%s'", errno, strerror(errno)); server.cache_save_pid = 0; } else { if (now - 30 > server.cache_save_time) { kill(server.cache_save_pid, SIGKILL); } return; } } if (check_time < 120) { check_time = 120; } if (now - check_time < server.cache_save_time) { return; } /* server is busy, skip*/ pthread_mutex_lock(&server.request_list_lock); if (list_empty(&server.request_list) != 0) { pthread_mutex_unlock(&server.request_list_lock); return; } pthread_mutex_unlock(&server.request_list_lock); server.cache_save_time = now; int pid = fork(); if (pid == 0) { /* child process */ for (int i = 3; i < 1024; i++) { close(i); } tlog_setlevel(TLOG_OFF); _dns_server_cache_save(1); _exit(0); } else if (pid < 0) { tlog(TLOG_DEBUG, "fork failed, errno %d, error info '%s'", errno, strerror(errno)); return; } server.cache_save_pid = pid; } static dns_cache_tmout_action_t _dns_server_cache_expired(struct dns_cache *dns_cache) { if (dns_cache->info.rcode != DNS_RC_NOERROR) { return DNS_CACHE_TMOUT_ACTION_DEL; } struct dns_conf_group *conf_group = dns_server_get_rule_group(dns_cache->info.dns_group_name); if (conf_group->dns_prefetch == 1) { if (conf_group->dns_serve_expired == 1) { return _dns_server_prefetch_expired_domain(conf_group, dns_cache); } else { return _dns_server_prefetch_domain(conf_group, dns_cache); } } return DNS_CACHE_TMOUT_ACTION_DEL; } int _dns_server_cache_init(void) { if (dns_cache_init(dns_conf.cachesize, dns_conf.cache_max_memsize, _dns_server_cache_expired) != 0) { tlog(TLOG_ERROR, "init cache failed."); return -1; } const char *dns_cache_file = dns_conf_get_cache_dir(); if (dns_conf.cache_persist == 2) { uint64_t freespace = get_free_space(dns_cache_file); if (freespace >= CACHE_AUTO_ENABLE_SIZE) { tlog(TLOG_INFO, "auto enable cache persist."); dns_conf.cache_persist = 1; } } if (dns_conf.cachesize <= 0 || dns_conf.cache_persist == 0) { return 0; } if (dns_cache_load(dns_cache_file) != 0) { tlog(TLOG_WARN, "Load cache failed."); return 0; } return 0; } int _dns_server_cache_save(int check_lock) { const char *dns_cache_file = dns_conf_get_cache_dir(); if (dns_conf.cache_persist == 0 || dns_conf.cachesize <= 0) { if (access(dns_cache_file, F_OK) == 0) { unlink(dns_cache_file); } return 0; } if (dns_cache_save(dns_cache_file, check_lock) != 0) { tlog(TLOG_WARN, "save cache failed."); return -1; } return 0; } ================================================ FILE: src/dns_server/cache.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_CACHE_ #define _DNS_SERVER_CACHE_ #include "dns_server.h" #include "smartdns/dns_cache.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_cache_save(int check_lock); void _dns_server_save_cache_to_file(void); int _dns_server_cache_init(void); int _dns_server_process_cache(struct dns_request *request); int _dns_cache_cname_packet(struct dns_server_post_context *context); int _dns_server_request_update_cache(struct dns_request *request, int speed, dns_type_t qtype, struct dns_cache_data *cache_data, int cache_ttl); int _dns_cache_packet(struct dns_server_post_context *context); int _dns_cache_try_keep_old_cache(struct dns_request *request); int _dns_cache_specify_packet(struct dns_server_post_context *context); int _dns_server_expired_cache_ttl(struct dns_cache *cache, int serve_expired_ttl); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/client_rule.c ================================================ /************************************************************************* * * 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 . */ #include "client_rule.h" #include "request.h" int _dns_server_request_set_client_rules(struct dns_request *request, struct dns_client_rules *client_rule) { if (client_rule == NULL) { if (_dns_server_has_bind_flag(request, BIND_FLAG_ACL) == 0 || dns_conf.acl_enable) { request->send_tick = get_tick_count(); request->rcode = DNS_RC_REFUSED; request->no_cache = 1; return -1; } return 0; } tlog(TLOG_DEBUG, "match client rule."); if (client_rule->rules[CLIENT_RULE_GROUP]) { struct client_rule_group *group = (struct client_rule_group *)client_rule->rules[CLIENT_RULE_GROUP]; if (group && group->group_name[0] != '\0') { safe_strncpy(request->dns_group_name, group->group_name, sizeof(request->dns_group_name)); } } if (client_rule->rules[CLIENT_RULE_FLAGS]) { struct client_rule_flags *flags = (struct client_rule_flags *)client_rule->rules[CLIENT_RULE_FLAGS]; if (flags) { request->server_flags = flags->flags; } } return 0; } ================================================ FILE: src/dns_server/client_rule.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_CLIENT_RULE_ #define _DNS_SERVER_CLIENT_RULE_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_request_set_client_rules(struct dns_request *request, struct dns_client_rules *client_rule); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/cname.c ================================================ /************************************************************************* * * 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 . */ #include "cname.h" #include "request.h" #include "rules.h" static DNS_CHILD_POST_RESULT _dns_server_process_cname_callback(struct dns_request *request, struct dns_request *child_request, int is_first_resp) { _dns_server_request_copy(request, child_request); if (child_request->rcode == DNS_RC_NOERROR && request->conf->dns_force_no_cname == 0 && child_request->has_soa == 0) { safe_strncpy(request->cname, child_request->domain, sizeof(request->cname)); request->has_cname = 1; request->ttl_cname = _dns_server_get_conf_ttl(request, child_request->ip_ttl); } return DNS_CHILD_POST_SUCCESS; } int _dns_server_process_cname_pre(struct dns_request *request) { struct dns_cname_rule *cname = NULL; struct dns_request_domain_rule domain_rule; uint32_t flags = _dns_server_get_rule_flags(request); if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_CNAME) == 0) { return 0; } if (request->has_cname_loop == 1) { return 0; } /* get domain rule flag */ if (flags & DOMAIN_FLAG_CNAME_IGN) { return 0; } cname = _dns_server_get_dns_rule(request, DOMAIN_RULE_CNAME); if (cname == NULL) { return 0; } request->skip_domain_rule = 0; /* copy child rules */ memcpy(&domain_rule, &request->domain_rule, sizeof(domain_rule)); memset(&request->domain_rule, 0, sizeof(request->domain_rule)); _dns_server_get_domain_rule_by_domain(request, cname->cname, 0); request->domain_rule.rules[DOMAIN_RULE_CNAME] = domain_rule.rules[DOMAIN_RULE_CNAME]; request->domain_rule.is_sub_rule[DOMAIN_RULE_CNAME] = domain_rule.is_sub_rule[DOMAIN_RULE_CNAME]; request->no_select_possible_ip = 1; request->no_cache_cname = 1; safe_strncpy(request->cname, cname->cname, sizeof(request->cname)); return 0; } int _dns_server_process_cname(struct dns_request *request) { struct dns_cname_rule *cname = NULL; const char *child_group_name = NULL; int ret = 0; struct dns_rule_flags *rule_flag = NULL; if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_CNAME) == 0) { return 0; } if (request->has_cname_loop == 1) { return 0; } /* get domain rule flag */ rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); if (rule_flag != NULL) { if (rule_flag->flags & DOMAIN_FLAG_CNAME_IGN) { return 0; } } cname = _dns_server_get_dns_rule(request, DOMAIN_RULE_CNAME); if (cname == NULL) { return 0; } tlog(TLOG_INFO, "query %s with cname %s", request->domain, cname->cname); struct dns_request *child_request = _dns_server_new_child_request(request, cname->cname, request->qtype, _dns_server_process_cname_callback); if (child_request == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); return -1; } /* check cname rule loop */ struct dns_request *check_request = child_request->parent_request; struct dns_cname_rule *child_cname = _dns_server_get_dns_rule(child_request, DOMAIN_RULE_CNAME); /* sub domain rule*/ if (child_cname != NULL && strncasecmp(child_request->domain, child_cname->cname, DNS_MAX_CNAME_LEN) == 0) { child_request->domain_rule.rules[DOMAIN_RULE_CNAME] = NULL; child_request->has_cname_loop = 1; } /* loop rule */ while (check_request != NULL && child_cname != NULL) { struct dns_cname_rule *check_cname = _dns_server_get_dns_rule(check_request, DOMAIN_RULE_CNAME); if (check_cname == NULL) { break; } if (strstr(child_request->domain, check_request->domain) != NULL && check_request != child_request->parent_request) { child_request->domain_rule.rules[DOMAIN_RULE_CNAME] = NULL; child_request->has_cname_loop = 1; break; } check_request = check_request->parent_request; } /* query cname domain */ if (child_request->has_cname_loop == 1 && strncasecmp(request->domain, cname->cname, DNS_MAX_CNAME_LEN) == 0) { request->has_cname_loop = 0; request->domain_rule.rules[DOMAIN_RULE_CNAME] = NULL; tlog(TLOG_DEBUG, "query cname domain %s", request->domain); goto out; } child_group_name = _dns_server_get_request_server_groupname(child_request); if (child_group_name) { /* reset dns group and setup child request domain group again when do query.*/ child_request->dns_group_name[0] = '\0'; } request->request_wait++; ret = _dns_server_do_query(child_request, 0); if (ret != 0) { request->request_wait--; tlog(TLOG_ERROR, "do query %s type %d failed.\n", request->domain, request->qtype); goto errout; } _dns_server_request_release_complete(child_request, 0); return 1; errout: if (child_request) { request->child_request = NULL; _dns_server_request_release(child_request); } return -1; out: if (child_request) { child_request->parent_request = NULL; request->child_request = NULL; _dns_server_request_release(child_request); _dns_server_request_release(request); } return 0; } ================================================ FILE: src/dns_server/cname.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_CNAME_ #define _DNS_SERVER_CNAME_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_process_cname_pre(struct dns_request *request); int _dns_server_process_cname(struct dns_request *request); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/connection.c ================================================ /************************************************************************* * * 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 . */ #include "connection.h" #include "dns_server.h" #include "server_http2.h" #include "smartdns/http2.h" #include #include #include int _dns_server_epoll_ctl(struct dns_server_conn_head *head, int op, uint32_t events) { struct epoll_event event; memset(&event, 0, sizeof(event)); event.events = events; event.data.ptr = head; if (epoll_ctl(server.epoll_fd, op, head->fd, &event) != 0) { return -1; } return 0; } void _dns_server_conn_release(struct dns_server_conn_head *conn) { if (conn == NULL) { return; } int refcnt = atomic_dec_return(&conn->refcnt); if (refcnt) { if (refcnt < 0) { BUG("BUG: refcnt is %d, type = %d", refcnt, conn->type); } return; } if (conn->type == DNS_CONN_TYPE_TLS_CLIENT || conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) { struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)conn; if (tls_client->ssl != NULL) { SSL_free(tls_client->ssl); tls_client->ssl = NULL; } if (tls_client->http2_ctx != NULL) { http2_ctx_put(tls_client->http2_ctx); tls_client->http2_ctx = NULL; } pthread_mutex_destroy(&tls_client->ssl_lock); } else if (conn->type == DNS_CONN_TYPE_TLS_SERVER || conn->type == DNS_CONN_TYPE_HTTPS_SERVER) { struct dns_server_conn_tls_server *tls_server = (struct dns_server_conn_tls_server *)conn; if (tls_server->ssl_ctx != NULL) { SSL_CTX_free(tls_server->ssl_ctx); tls_server->ssl_ctx = NULL; } } else if (conn->type == DNS_CONN_TYPE_HTTP2_STREAM) { struct dns_server_conn_http2_stream *http2_stream = (struct dns_server_conn_http2_stream *)conn; if (http2_stream->stream != NULL) { http2_stream_close(http2_stream->stream); http2_stream->stream = NULL; } } if (conn->fd > 0) { close(conn->fd); conn->fd = -1; } pthread_mutex_lock(&server.conn_list_lock); list_del_init(&conn->list); pthread_mutex_unlock(&server.conn_list_lock); free(conn); } void _dns_server_conn_get(struct dns_server_conn_head *conn) { if (conn == NULL) { return; } if (atomic_inc_return(&conn->refcnt) <= 0) { BUG("BUG: client ref is invalid."); } } void _dns_server_close_socket(void) { struct dns_server_conn_head *conn = NULL; struct dns_server_conn_head *tmp = NULL; pthread_mutex_lock(&server.conn_list_lock); list_for_each_entry_safe(conn, tmp, &server.conn_list, list) { /* Force cleanup of TLS/HTTPS client connections to prevent memory leaks */ if (conn->type == DNS_CONN_TYPE_TLS_CLIENT || conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) { struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)conn; /* Free SSL connection */ if (tls_client->ssl != NULL) { SSL_free(tls_client->ssl); tls_client->ssl = NULL; } } _dns_server_client_close(conn); } pthread_mutex_unlock(&server.conn_list_lock); } void _dns_server_close_socket_server(void) { struct dns_server_conn_head *conn = NULL; struct dns_server_conn_head *tmp = NULL; list_for_each_entry_safe(conn, tmp, &server.conn_list, list) { switch (conn->type) { case DNS_CONN_TYPE_HTTPS_SERVER: case DNS_CONN_TYPE_TLS_SERVER: { struct dns_server_conn_tls_server *tls_server = (struct dns_server_conn_tls_server *)conn; if (tls_server->ssl_ctx) { SSL_CTX_free(tls_server->ssl_ctx); tls_server->ssl_ctx = NULL; } _dns_server_client_close(conn); break; } case DNS_CONN_TYPE_UDP_SERVER: case DNS_CONN_TYPE_TCP_SERVER: _dns_server_client_close(conn); break; default: break; } } } void _dns_server_client_touch(struct dns_server_conn_head *conn) { time(&conn->last_request_time); } int _dns_server_client_close(struct dns_server_conn_head *conn) { if (conn->fd > 0) { _dns_server_epoll_ctl(conn, EPOLL_CTL_DEL, 0); } pthread_mutex_lock(&server.conn_list_lock); list_del_init(&conn->list); pthread_mutex_unlock(&server.conn_list_lock); if (conn->type == DNS_CONN_TYPE_TLS_CLIENT || conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) { struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)conn; if (tls_client->http2_ctx != NULL) { http2_ctx_close(tls_client->http2_ctx); tls_client->http2_ctx = NULL; } } _dns_server_conn_release(conn); return 0; } int _dns_server_update_request_connection_timeout(struct dns_server_conn_head *conn, int timeout) { if (conn == NULL) { return -1; } if (timeout == 0) { return 0; } switch (conn->type) { case DNS_CONN_TYPE_TCP_CLIENT: { struct dns_server_conn_tcp_client *tcpclient = (struct dns_server_conn_tcp_client *)conn; tcpclient->conn_idle_timeout = timeout; } break; case DNS_CONN_TYPE_TLS_CLIENT: case DNS_CONN_TYPE_HTTPS_CLIENT: { struct dns_server_conn_tls_client *tlsclient = (struct dns_server_conn_tls_client *)conn; tlsclient->tcp.conn_idle_timeout = timeout; } break; default: break; } return 0; } void _dns_server_conn_head_init(struct dns_server_conn_head *conn, int fd, int type) { memset(conn, 0, sizeof(*conn)); conn->fd = fd; conn->type = type; atomic_set(&conn->refcnt, 0); INIT_LIST_HEAD(&conn->list); } int _dns_server_set_flags(struct dns_server_conn_head *head, struct dns_bind_ip *bind_ip) { time(&head->last_request_time); head->server_flags = bind_ip->flags; head->dns_group = bind_ip->group; head->ipset_nftset_rule = &bind_ip->nftset_ipset_rule; atomic_set(&head->refcnt, 0); list_add(&head->list, &server.conn_list); return 0; } ================================================ FILE: src/dns_server/connection.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_CONNECTION_ #define _DNS_SERVER_CONNECTION_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_server_close_socket_server(void); int _dns_server_client_close(struct dns_server_conn_head *conn); void _dns_server_client_touch(struct dns_server_conn_head *conn); int _dns_server_set_flags(struct dns_server_conn_head *head, struct dns_bind_ip *bind_ip); void _dns_server_conn_head_init(struct dns_server_conn_head *conn, int fd, int type); void _dns_server_conn_get(struct dns_server_conn_head *conn); void _dns_server_conn_release(struct dns_server_conn_head *conn); int _dns_server_epoll_ctl(struct dns_server_conn_head *head, int op, uint32_t events); void _dns_server_close_socket(void); int _dns_server_update_request_connection_timeout(struct dns_server_conn_head *conn, int timeout); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/context.c ================================================ /************************************************************************* * * 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 . */ #include "context.h" #include "smartdns/dns_conf.h" #include "address.h" #include "audit.h" #include "cache.h" #include "dns_server.h" #include "ip_rule.h" #include "ipset_nftset.h" #include "request.h" #include "request_pending.h" #include "rules.h" #include "soa.h" void _dns_server_post_context_init(struct dns_server_post_context *context, struct dns_request *request) { memset(context, 0, sizeof(*context)); context->packet = (struct dns_packet *)(context->packet_buff); context->packet_maxlen = sizeof(context->packet_buff); context->inpacket = (unsigned char *)(context->inpacket_buff); context->inpacket_maxlen = sizeof(context->inpacket_buff); context->qtype = request->qtype; context->request = request; } static void _dns_server_context_add_ip(struct dns_server_post_context *context, const unsigned char *ip_addr) { if (context->ip_num < MAX_IP_NUM) { context->ip_addr[context->ip_num] = ip_addr; } context->ip_num++; } void _dns_server_post_context_init_from(struct dns_server_post_context *context, struct dns_request *request, struct dns_packet *packet, unsigned char *inpacket, int inpacket_len) { memset(context, 0, sizeof(*context)); context->packet = packet; context->packet_maxlen = sizeof(context->packet_buff); context->inpacket = inpacket; context->inpacket_len = inpacket_len; context->inpacket_maxlen = sizeof(context->inpacket); context->qtype = request->qtype; context->request = request; } static void _dns_rrs_result_log(struct dns_server_post_context *context, struct dns_ip_address *addr_map) { struct dns_request *request = context->request; if (context->do_log_result == 0 || addr_map == NULL) { return; } if (addr_map->addr_type == DNS_T_A) { tlog(TLOG_INFO, "result: %s, id: %d, index: %d, rtt: %.1f ms, %d.%d.%d.%d", request->domain, request->id, context->ip_num, ((float)addr_map->ping_time) / 10, addr_map->ip_addr[0], addr_map->ip_addr[1], addr_map->ip_addr[2], addr_map->ip_addr[3]); } else if (addr_map->addr_type == DNS_T_AAAA) { tlog(TLOG_INFO, "result: %s, id: %d, index: %d, rtt: %.1f ms, " "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", request->domain, request->id, context->ip_num, ((float)addr_map->ping_time) / 10, addr_map->ip_addr[0], addr_map->ip_addr[1], addr_map->ip_addr[2], addr_map->ip_addr[3], addr_map->ip_addr[4], addr_map->ip_addr[5], addr_map->ip_addr[6], addr_map->ip_addr[7], addr_map->ip_addr[8], addr_map->ip_addr[9], addr_map->ip_addr[10], addr_map->ip_addr[11], addr_map->ip_addr[12], addr_map->ip_addr[13], addr_map->ip_addr[14], addr_map->ip_addr[15]); } } static int _dns_rrs_add_all_best_ip(struct dns_server_post_context *context) { struct dns_ip_address *addr_map = NULL; struct dns_ip_address *added_ip_addr = NULL; struct hlist_node *tmp = NULL; struct dns_request *request = context->request; unsigned long bucket = 0; char *domain = NULL; int ret = 0; int ignore_speed = 0; int maxhit = 0; if (context->select_all_best_ip == 0 || context->ip_num >= request->conf->dns_max_reply_ip_num) { return 0; } domain = request->domain; /* add CNAME record */ if (request->has_cname) { domain = request->cname; } /* add fasted ip address at first place of dns RR */ if (request->has_ip) { added_ip_addr = _dns_ip_address_get(request, request->ip_addr, request->qtype); _dns_rrs_result_log(context, added_ip_addr); } if (request->passthrough == 2) { ignore_speed = 1; } while (true) { pthread_mutex_lock(&request->ip_map_lock); hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node) { if (context->ip_num >= request->conf->dns_max_reply_ip_num) { break; } if (context->qtype != addr_map->addr_type) { continue; } if (addr_map == added_ip_addr) { continue; } if (addr_map->hitnum > maxhit) { maxhit = addr_map->hitnum; } if (addr_map->ping_time < 0 && ignore_speed == 0) { continue; } if (addr_map->hitnum < maxhit && ignore_speed == 1) { continue; } /* if ping time is larger than 5ms, check again. */ if (addr_map->ping_time - request->ping_time >= 50) { int ttl_range = request->ping_time + request->ping_time / 10 + 5; if ((ttl_range < addr_map->ping_time) && addr_map->ping_time >= 100 && ignore_speed == 0) { continue; } } _dns_server_context_add_ip(context, addr_map->ip_addr); if (addr_map->addr_type == DNS_T_A) { ret |= dns_add_A(context->packet, DNS_RRS_AN, domain, request->ip_ttl, addr_map->ip_addr); } else if (addr_map->addr_type == DNS_T_AAAA) { ret |= dns_add_AAAA(context->packet, DNS_RRS_AN, domain, request->ip_ttl, addr_map->ip_addr); } _dns_rrs_result_log(context, addr_map); } pthread_mutex_unlock(&request->ip_map_lock); if (context->ip_num <= 0 && ignore_speed == 0) { ignore_speed = 1; } else { break; } } return ret; } static int _dns_server_add_srv(struct dns_server_post_context *context) { struct dns_request *request = context->request; struct dns_request_srv *srv = NULL; int ret = 0; list_for_each_entry(srv, &request->srv_list, list) { ret = dns_add_SRV(context->packet, DNS_RRS_AN, request->domain, request->ip_ttl, srv->priority, srv->weight, srv->port, srv->host); if (ret != 0) { return -1; } } return 0; } static int _dns_add_rrs_ip_hint(struct dns_server_post_context *context, struct dns_rr_nested *param, dns_type_t qtype) { typedef int (*addfunc)(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num); struct dns_request *request = context->request; struct dns_ip_address *addr_map = NULL; unsigned long bucket = 0; struct hlist_node *tmp = NULL; int ret = 0; int all_ips = 0; int addr_num = 0; addfunc add_func = NULL; unsigned char *addr[8]; int addr_buffer_size = sizeof(addr) / sizeof(addr[0]); if (qtype == DNS_T_A) { add_func = dns_HTTPS_add_ipv4hint; } else if (qtype == DNS_T_AAAA) { add_func = dns_HTTPS_add_ipv6hint; } else { return 0; // Unsupported type } if (request->passthrough == 2) { all_ips = 1; } if (request->has_ip == 0) { return 0; } if (all_ips == 0) { if (request->ip_addr_type == (int)qtype) { addr[0] = request->ip_addr; ret = add_func(param, addr, 1); return ret; } return 0; } ret = 0; pthread_mutex_lock(&request->ip_map_lock); hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node) { if (addr_map->addr_type == qtype) { addr[addr_num] = addr_map->ip_addr; addr_num++; if (addr_num >= addr_buffer_size) { break; } } } pthread_mutex_unlock(&request->ip_map_lock); if (addr_num > 0) { ret = add_func(param, addr, addr_num); } return ret; } static int _dns_add_rrs_HTTPS(struct dns_server_post_context *context) { struct dns_request *request = context->request; struct dns_request_https *https_svcb; int ret = 0; struct dns_rr_nested param; if (request->qtype != DNS_T_HTTPS) { return 0; } list_for_each_entry(https_svcb, &request->https_svcb_list, list) { ret = dns_add_HTTPS_start(¶m, context->packet, DNS_RRS_AN, https_svcb->domain, https_svcb->ttl, https_svcb->priority, https_svcb->target); if (ret != 0) { return ret; } if (https_svcb->alpn[0] != '\0' && https_svcb->alpn_len > 0) { ret = dns_HTTPS_add_alpn(¶m, https_svcb->alpn, https_svcb->alpn_len); if (ret != 0) { return ret; } } if (https_svcb->port != 0) { ret = dns_HTTPS_add_port(¶m, https_svcb->port); if (ret != 0) { return ret; } } if (https_svcb->has_ipv4) { unsigned char *addr[1]; addr[0] = https_svcb->ipv4_addr; ret = dns_HTTPS_add_ipv4hint(¶m, addr, 1); } else { ret = _dns_add_rrs_ip_hint(context, ¶m, DNS_T_A); } if (ret != 0) { return ret; } if (https_svcb->ech_len > 0) { ret = dns_HTTPS_add_ech(¶m, https_svcb->ech, https_svcb->ech_len); if (ret != 0) { return ret; } } if (https_svcb->has_ipv6) { unsigned char *addr[1]; addr[0] = https_svcb->ipv6_addr; ret = dns_HTTPS_add_ipv6hint(¶m, addr, 1); } else { ret = _dns_add_rrs_ip_hint(context, ¶m, DNS_T_AAAA); } if (ret != 0) { return ret; } dns_add_HTTPS_end(¶m); } return 0; } static int _dns_add_rrs(struct dns_server_post_context *context) { struct dns_request *request = context->request; int ret = 0; int has_soa = request->has_soa; char *domain = request->domain; if (request->has_ptr) { /* add PTR record */ ret = dns_add_PTR(context->packet, DNS_RRS_AN, request->domain, request->ip_ttl, request->ptr_hostname); } /* add CNAME record */ if (request->has_cname && context->do_force_soa == 0) { ret |= dns_add_CNAME(context->packet, DNS_RRS_AN, request->domain, request->ttl_cname, request->cname); domain = request->cname; } if (!list_empty(&request->https_svcb_list)) { ret = _dns_add_rrs_HTTPS(context); } /* add A record */ if (request->has_ip && context->do_force_soa == 0) { _dns_server_context_add_ip(context, request->ip_addr); if (context->qtype == DNS_T_A) { ret |= dns_add_A(context->packet, DNS_RRS_AN, domain, request->ip_ttl, request->ip_addr); tlog(TLOG_DEBUG, "result: %s, rtt: %.1f ms, %d.%d.%d.%d", request->domain, ((float)request->ping_time) / 10, request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3]); } /* add AAAA record */ if (context->qtype == DNS_T_AAAA) { ret |= dns_add_AAAA(context->packet, DNS_RRS_AN, domain, request->ip_ttl, request->ip_addr); tlog(TLOG_DEBUG, "result: %s, rtt: %.1f ms, " "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", request->domain, ((float)request->ping_time) / 10, request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3], request->ip_addr[4], request->ip_addr[5], request->ip_addr[6], request->ip_addr[7], request->ip_addr[8], request->ip_addr[9], request->ip_addr[10], request->ip_addr[11], request->ip_addr[12], request->ip_addr[13], request->ip_addr[14], request->ip_addr[15]); } } if (context->do_force_soa == 0) { ret |= _dns_rrs_add_all_best_ip(context); } if (context->qtype == DNS_T_A || context->qtype == DNS_T_AAAA) { if (context->ip_num > 0) { has_soa = 0; } } /* add SOA record */ if (has_soa) { ret |= dns_add_SOA(context->packet, DNS_RRS_NS, domain, request->ip_ttl, &request->soa); tlog(TLOG_DEBUG, "result: %s, qtype: %d, return SOA", request->domain, context->qtype); } else if (context->do_force_soa == 1) { _dns_server_setup_soa(request); ret |= dns_add_SOA(context->packet, DNS_RRS_NS, domain, request->ip_ttl, &request->soa); } if (request->has_ecs) { ret |= dns_add_OPT_ECS(context->packet, &request->ecs); } if (!list_empty(&request->srv_list)) { ret |= _dns_server_add_srv(context); } if (request->rcode != DNS_RC_NOERROR) { tlog(TLOG_INFO, "result: %s, qtype: %d, rtcode: %d, id: %d", domain, context->qtype, request->rcode, request->id); } return ret; } static int _dns_setup_dns_packet(struct dns_server_post_context *context) { struct dns_head head; struct dns_request *request = context->request; int ret = 0; memset(&head, 0, sizeof(head)); head.id = request->id; head.qr = DNS_QR_ANSWER; head.opcode = DNS_OP_QUERY; head.rd = 1; head.ra = 1; head.aa = 0; head.tc = 0; head.rcode = request->rcode; /* init a new DNS packet */ ret = dns_packet_init(context->packet, context->packet_maxlen, &head); if (ret != 0) { return -1; } if (request->domain[0] == '\0') { return 0; } /* add request domain */ ret = dns_add_domain(context->packet, request->domain, context->qtype, request->qclass); if (ret != 0) { return -1; } /* add RECORDs */ ret = _dns_add_rrs(context); if (ret != 0) { return -1; } return 0; } static int _dns_setup_dns_raw_packet(struct dns_server_post_context *context) { /* encode to binary data */ int encode_len = dns_encode(context->inpacket, context->inpacket_maxlen, context->packet); if (encode_len <= 0) { tlog(TLOG_DEBUG, "encode raw packet failed for %s", context->request->domain); return -1; } context->inpacket_len = encode_len; return 0; } static int _dns_result_callback(struct dns_server_post_context *context) { struct dns_result result; char ip[DNS_MAX_CNAME_LEN]; unsigned int ping_time = -1; struct dns_request *request = context->request; if (request->result_callback == NULL) { return 0; } if (atomic_inc_return(&request->do_callback) != 1) { return 0; } ip[0] = 0; memset(&result, 0, sizeof(result)); ping_time = request->ping_time; result.domain = request->domain; result.rtcode = request->rcode; result.addr_type = request->qtype; result.ip = ip; result.has_soa = request->has_soa | context->do_force_soa; result.ping_time = ping_time; result.ip_num = 0; if (request->has_ip != 0 && context->do_force_soa == 0) { for (int i = 0; i < context->ip_num && i < MAX_IP_NUM; i++) { result.ip_addr[i] = context->ip_addr[i]; result.ip_num++; } if (request->qtype == DNS_T_A) { snprintf(ip, sizeof(ip), "%d.%d.%d.%d", request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3]); } else if (request->qtype == DNS_T_AAAA) { snprintf(ip, sizeof(ip), "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3], request->ip_addr[4], request->ip_addr[5], request->ip_addr[6], request->ip_addr[7], request->ip_addr[8], request->ip_addr[9], request->ip_addr[10], request->ip_addr[11], request->ip_addr[12], request->ip_addr[13], request->ip_addr[14], request->ip_addr[15]); } } return request->result_callback(&result, request->user_ptr); } static int _dns_server_setup_ipset_nftset_packet(struct dns_server_post_context *context) { int ttl = 0; struct dns_request *request = context->request; char name[DNS_MAX_CNAME_LEN] = {0}; int rr_count = 0; int timeout_value = 0; int ipset_timeout_value = 0; int nftset_timeout_value = 0; int i = 0; int j = 0; struct dns_conf_group *conf; struct dns_rrs *rrs = NULL; struct dns_ipset_rule *rule = NULL; struct dns_ipset_rule *ipset_rule = NULL; struct dns_ipset_rule *ipset_rule_v4 = NULL; struct dns_ipset_rule *ipset_rule_v6 = NULL; struct dns_nftset_rule *nftset_ip = NULL; struct dns_nftset_rule *nftset_ip6 = NULL; struct dns_rule_flags *rule_flags = NULL; int check_no_speed_rule = 0; if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_IPSET) == 0) { return 0; } if (context->do_ipset == 0) { return 0; } if (context->ip_num <= 0) { return 0; } if (request->ping_time < 0 && request->has_ip > 0 && request->passthrough == 0) { check_no_speed_rule = 1; } conf = request->conf; /* check ipset rule */ rule_flags = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); if (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_IPSET_IGN) == 0) { ipset_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_IPSET); if (ipset_rule == NULL) { ipset_rule = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_IPSET); } if (ipset_rule == NULL && check_no_speed_rule && conf->ipset_nftset.ipset_no_speed.inet_enable) { ipset_rule_v4 = &conf->ipset_nftset.ipset_no_speed.inet; } } if (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_IPSET_IPV4_IGN) == 0) { ipset_rule_v4 = _dns_server_get_dns_rule(request, DOMAIN_RULE_IPSET_IPV4); if (ipset_rule_v4 == NULL) { ipset_rule_v4 = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_IPSET_IPV4); } if (ipset_rule_v4 == NULL && check_no_speed_rule && conf->ipset_nftset.ipset_no_speed.ipv4_enable) { ipset_rule_v4 = &conf->ipset_nftset.ipset_no_speed.ipv4; } } if (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_IPSET_IPV6_IGN) == 0) { ipset_rule_v6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_IPSET_IPV6); if (ipset_rule_v6 == NULL) { ipset_rule_v6 = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_IPSET_IPV6); } if (ipset_rule_v6 == NULL && check_no_speed_rule && conf->ipset_nftset.ipset_no_speed.ipv6_enable) { ipset_rule_v6 = &conf->ipset_nftset.ipset_no_speed.ipv6; } } if (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_NFTSET_IP_IGN) == 0) { nftset_ip = _dns_server_get_dns_rule(request, DOMAIN_RULE_NFTSET_IP); if (nftset_ip == NULL) { nftset_ip = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_NFTSET_IP); } if (nftset_ip == NULL && check_no_speed_rule && conf->ipset_nftset.nftset_no_speed.ip_enable) { nftset_ip = &conf->ipset_nftset.nftset_no_speed.ip; } } if (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_NFTSET_IP6_IGN) == 0) { nftset_ip6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_NFTSET_IP6); if (nftset_ip6 == NULL) { nftset_ip6 = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_NFTSET_IP6); } if (nftset_ip6 == NULL && check_no_speed_rule && conf->ipset_nftset.nftset_no_speed.ip6_enable) { nftset_ip6 = &conf->ipset_nftset.nftset_no_speed.ip6; } } if (!(ipset_rule || ipset_rule_v4 || ipset_rule_v6 || nftset_ip || nftset_ip6)) { return 0; } timeout_value = request->ip_ttl * 3; if (timeout_value == 0) { timeout_value = _dns_server_get_conf_ttl(request, 0) * 3; } if (conf->ipset_nftset.ipset_timeout_enable) { ipset_timeout_value = timeout_value; } if (conf->ipset_nftset.nftset_timeout_enable) { nftset_timeout_value = timeout_value; } for (j = 1; j < DNS_RRS_OPT; j++) { rrs = dns_get_rrs_start(context->packet, j, &rr_count); for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(context->packet, rrs)) { switch (rrs->type) { case DNS_T_A: { unsigned char addr[4]; if (context->qtype != DNS_T_A) { break; } /* get A result */ dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr); rule = ipset_rule_v4 ? ipset_rule_v4 : ipset_rule; _dns_server_add_ipset_nftset(request, rule, nftset_ip, addr, DNS_RR_A_LEN, ipset_timeout_value, nftset_timeout_value); } break; case DNS_T_AAAA: { unsigned char addr[16]; if (context->qtype != DNS_T_AAAA) { /* ignore non-matched query type */ break; } dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr); rule = ipset_rule_v6 ? ipset_rule_v6 : ipset_rule; _dns_server_add_ipset_nftset(request, rule, nftset_ip6, addr, DNS_RR_AAAA_LEN, ipset_timeout_value, nftset_timeout_value); } break; case DNS_T_HTTPS: { char target[DNS_MAX_CNAME_LEN] = {0}; struct dns_svcparam *p = NULL; int priority = 0; int ret = dns_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN); if (ret != 0) { tlog(TLOG_WARN, "get HTTPS svcparm failed"); return -1; } for (; p; p = dns_svcparm_next(rrs, p)) { switch (p->key) { case DNS_HTTPS_T_IPV4HINT: { unsigned char *addr; for (int k = 0; k < p->len / 4; k++) { addr = p->value + k * 4; rule = ipset_rule_v4 ? ipset_rule_v4 : ipset_rule; _dns_server_add_ipset_nftset(request, rule, nftset_ip, addr, DNS_RR_A_LEN, ipset_timeout_value, nftset_timeout_value); } } break; case DNS_HTTPS_T_IPV6HINT: { unsigned char *addr; for (int k = 0; k < p->len / 16; k++) { addr = p->value + k * 16; rule = ipset_rule_v6 ? ipset_rule_v6 : ipset_rule; _dns_server_add_ipset_nftset(request, rule, nftset_ip6, addr, DNS_RR_AAAA_LEN, ipset_timeout_value, nftset_timeout_value); } } break; default: break; } } } break; default: break; } } } return 0; } static int _dns_result_child_post(struct dns_server_post_context *context) { struct dns_request *request = context->request; struct dns_request *parent_request = request->parent_request; DNS_CHILD_POST_RESULT child_ret = DNS_CHILD_POST_FAIL; /* not a child request */ if (parent_request == NULL) { return 0; } if (request->child_callback) { int is_first_resp = context->no_release_parent; child_ret = request->child_callback(parent_request, request, is_first_resp); } if (context->do_reply == 1 && child_ret == DNS_CHILD_POST_SUCCESS) { struct dns_server_post_context parent_context; _dns_server_post_context_init(&parent_context, parent_request); parent_context.do_cache = context->do_cache; parent_context.do_ipset = context->do_ipset; parent_context.do_force_soa = context->do_force_soa; parent_context.do_audit = context->do_audit; parent_context.do_reply = context->do_reply; parent_context.reply_ttl = context->reply_ttl; parent_context.cache_ttl = context->cache_ttl; parent_context.skip_notify_count = context->skip_notify_count; parent_context.select_all_best_ip = 1; parent_context.no_release_parent = context->no_release_parent; _dns_request_post(&parent_context); _dns_server_reply_all_pending_list(parent_request, &parent_context); } if (context->no_release_parent == 0) { tlog(TLOG_DEBUG, "query %s with child %s done", parent_request->domain, request->domain); request->parent_request = NULL; parent_request->request_wait--; _dns_server_request_release(parent_request); } if (child_ret == DNS_CHILD_POST_FAIL) { return -1; } return 0; } static int _dns_request_update_id_ttl_domain(struct dns_server_post_context *context) { int ttl = context->reply_ttl; struct dns_request *request = context->request; if (request->conf->dns_rr_ttl_reply_max > 0) { if (request->ip_ttl > request->conf->dns_rr_ttl_reply_max && ttl == 0) { ttl = request->ip_ttl; } if (ttl > request->conf->dns_rr_ttl_reply_max) { ttl = request->conf->dns_rr_ttl_reply_max; } if (ttl == 0) { ttl = request->conf->dns_rr_ttl_reply_max; } } if (ttl == 0) { ttl = request->ip_ttl; if (ttl == 0) { ttl = _dns_server_get_conf_ttl(request, ttl); } } struct dns_update_param param; param.id = request->id; param.cname_ttl = ttl; param.ip_ttl = ttl; param.query_domain = request->original_domain; if (dns_packet_update(context->inpacket, context->inpacket_len, ¶m) != 0) { tlog(TLOG_DEBUG, "update packet info failed."); } return 0; } int _dns_request_post(struct dns_server_post_context *context) { struct dns_request *request = context->request; char clientip[DNS_MAX_CNAME_LEN] = {0}; int ret = 0; tlog(TLOG_DEBUG, "reply %s qtype: %d, rcode: %d, reply: %d", request->domain, request->qtype, context->packet->head.rcode, context->do_reply); /* init a new DNS packet */ ret = _dns_setup_dns_packet(context); if (ret != 0) { tlog(TLOG_ERROR, "setup dns packet failed."); return -1; } ret = _dns_setup_dns_raw_packet(context); if (ret != 0) { tlog(TLOG_ERROR, "set dns raw packet failed."); return -1; } /* cache reply packet */ ret = _dns_cache_reply_packet(context); if (ret != 0) { tlog(TLOG_WARN, "cache packet for %s failed.", request->domain); } /* setup ipset */ _dns_server_setup_ipset_nftset_packet(context); /* reply child request */ _dns_result_child_post(context); if (context->do_reply == 0) { return 0; } if (context->skip_notify_count == 0) { if (atomic_inc_return(&request->notified) != 1) { tlog(TLOG_DEBUG, "skip reply %s %d", request->domain, request->qtype); return 0; } } /* log audit log */ _dns_server_audit_log(context); /* reply API callback */ _dns_result_callback(context); if (request->conn == NULL) { return 0; } ret = _dns_request_update_id_ttl_domain(context); if (ret != 0) { tlog(TLOG_ERROR, "update packet ttl failed."); return -1; } tlog(TLOG_INFO, "result: %s, client: %s, qtype: %d, id: %d, group: %s, time: %lums", request->domain, get_host_by_addr(clientip, sizeof(clientip), (struct sockaddr *)&request->addr), request->qtype, request->id, request->dns_group_name[0] != '\0' ? request->dns_group_name : DNS_SERVER_GROUP_DEFAULT, get_tick_count() - request->send_tick); ret = _dns_reply_inpacket(request, context->inpacket, context->inpacket_len); if (ret != 0) { tlog(TLOG_DEBUG, "reply raw packet to client failed."); return -1; } return 0; } int _dns_server_get_answer(struct dns_server_post_context *context) { int i = 0; int j = 0; int ttl = 0; struct dns_rrs *rrs = NULL; int rr_count = 0; struct dns_request *request = context->request; struct dns_packet *packet = context->packet; for (j = 1; j < DNS_RRS_OPT; j++) { rrs = dns_get_rrs_start(packet, j, &rr_count); for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { switch (rrs->type) { case DNS_T_A: { unsigned char addr[4]; char name[DNS_MAX_CNAME_LEN] = {0}; struct dns_ip_address *addr_map = NULL; if (request->qtype != DNS_T_A) { continue; } /* get A result */ dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr); if (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 && strncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) { continue; } if (context->no_check_add_ip == 0 && _dns_ip_address_check_add(request, name, addr, DNS_T_A, request->ping_time, &addr_map) != 0) { continue; } if (addr_map != NULL) { _dns_server_context_add_ip(context, addr_map->ip_addr); } if (request->has_ip == 1) { continue; } memcpy(request->ip_addr, addr, DNS_RR_A_LEN); /* add this ip to request */ request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); request->has_ip = 1; request->rcode = packet->head.rcode; } break; case DNS_T_AAAA: { unsigned char addr[16]; char name[DNS_MAX_CNAME_LEN] = {0}; struct dns_ip_address *addr_map = NULL; if (request->qtype != DNS_T_AAAA) { /* ignore non-matched query type */ continue; } dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr); if (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 && strncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) { continue; } if (context->no_check_add_ip == 0 && _dns_ip_address_check_add(request, name, addr, DNS_T_AAAA, request->ping_time, &addr_map) != 0) { continue; } if (addr_map != NULL) { _dns_server_context_add_ip(context, addr_map->ip_addr); } if (request->has_ip == 1) { continue; } memcpy(request->ip_addr, addr, DNS_RR_AAAA_LEN); request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); request->has_ip = 1; request->rcode = packet->head.rcode; } break; case DNS_T_NS: { char cname[DNS_MAX_CNAME_LEN]; char name[DNS_MAX_CNAME_LEN] = {0}; dns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN); tlog(TLOG_DEBUG, "NS: %s, ttl: %d, cname: %s\n", name, ttl, cname); } break; case DNS_T_CNAME: { char cname[DNS_MAX_CNAME_LEN]; char name[DNS_MAX_CNAME_LEN] = {0}; if (request->conf->dns_force_no_cname) { continue; } dns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN); tlog(TLOG_DEBUG, "name: %s, ttl: %d, cname: %s\n", name, ttl, cname); if (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 && strncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) { continue; } safe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN); request->ttl_cname = _dns_server_get_conf_ttl(request, ttl); request->has_cname = 1; } break; case DNS_T_SOA: { char name[DNS_MAX_CNAME_LEN] = {0}; request->has_soa = 1; if (request->rcode != DNS_RC_NOERROR) { request->rcode = packet->head.rcode; } dns_get_SOA(rrs, name, 128, &ttl, &request->soa); tlog(TLOG_DEBUG, "domain: %s, qtype: %d, SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, " "expire: " "%d, minimum: %d", request->domain, request->qtype, request->soa.mname, request->soa.rname, request->soa.serial, request->soa.refresh, request->soa.retry, request->soa.expire, request->soa.minimum); request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); } break; default: break; } } } return 0; } int _dns_cache_reply_packet(struct dns_server_post_context *context) { struct dns_request *request = context->request; int speed = -1; if (context->do_cache == 0 || request->no_cache == 1) { return 0; } if (context->packet->head.rcode == DNS_RC_SERVFAIL || context->packet->head.rcode == DNS_RC_NXDOMAIN || context->packet->head.rcode == DNS_RC_NOTIMP) { context->reply_ttl = DNS_SERVER_FAIL_TTL; /* Do not cache record if cannot connect to remote */ if (request->remote_server_fail == 0 && context->packet->head.rcode == DNS_RC_SERVFAIL) { /* Try keep old cache if server fail */ _dns_cache_try_keep_old_cache(request); return 0; } if (context->packet->head.rcode == DNS_RC_NOTIMP) { return 0; } if (context->packet->head.rcode == DNS_RC_NXDOMAIN) { context->reply_ttl = 0; } return _dns_cache_packet(context); } if (context->qtype != DNS_T_AAAA && context->qtype != DNS_T_A && context->qtype != DNS_T_HTTPS) { return _dns_cache_specify_packet(context); } struct dns_cache_data *cache_packet = dns_cache_new_data_packet(context->inpacket, context->inpacket_len); if (cache_packet == NULL) { return -1; } speed = request->ping_time; if (context->do_force_soa) { speed = -1; } if (_dns_server_request_update_cache(request, speed, context->qtype, cache_packet, context->cache_ttl) != 0) { tlog(TLOG_WARN, "update packet cache failed."); } _dns_cache_cname_packet(context); return 0; } int _dns_server_reply_passthrough(struct dns_server_post_context *context) { struct dns_request *request = context->request; if (atomic_inc_return(&request->notified) != 1) { return 0; } _dns_server_get_answer(context); _dns_cache_reply_packet(context); if (_dns_server_setup_ipset_nftset_packet(context) != 0) { tlog(TLOG_DEBUG, "setup ipset failed."); } _dns_result_callback(context); _dns_server_audit_log(context); /* reply child request */ _dns_result_child_post(context); if (request->conn && context->do_reply == 1) { char clientip[DNS_MAX_CNAME_LEN] = {0}; /* When passthrough, modify the id to be the id of the client request. */ int ret = _dns_request_update_id_ttl_domain(context); if (ret != 0) { tlog(TLOG_ERROR, "update packet ttl failed."); return -1; } _dns_reply_inpacket(request, context->inpacket, context->inpacket_len); tlog(TLOG_INFO, "result: %s, client: %s, qtype: %d, id: %d, group: %s, time: %lums", request->domain, get_host_by_addr(clientip, sizeof(clientip), (struct sockaddr *)&request->addr), request->qtype, request->id, request->dns_group_name[0] != '\0' ? request->dns_group_name : DNS_SERVER_GROUP_DEFAULT, get_tick_count() - request->send_tick); } return _dns_server_reply_all_pending_list(request, context); } ================================================ FILE: src/dns_server/context.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_CONTEXT_ #define _DNS_SERVER_CONTEXT_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_request_post(struct dns_server_post_context *context); void _dns_server_post_context_init(struct dns_server_post_context *context, struct dns_request *request); int _dns_server_reply_passthrough(struct dns_server_post_context *context); int _dns_cache_reply_packet(struct dns_server_post_context *context); int _dns_server_get_answer(struct dns_server_post_context *context); void _dns_server_post_context_init_from(struct dns_server_post_context *context, struct dns_request *request, struct dns_packet *packet, unsigned char *inpacket, int inpacket_len); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/ddr.c ================================================ /************************************************************************* * * 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 . */ #include "ddr.h" #include "context.h" #include "dns_server.h" #include "request.h" #include "smartdns/dns.h" #include "smartdns/util.h" #include "soa.h" #include static const char *_ddr_get_alpn(const struct dns_bind_ip *bind_ip) { if (bind_ip->alpn[0] != '\0') { return bind_ip->alpn; } switch (bind_ip->type) { case DNS_BIND_TYPE_TLS: return "dot"; case DNS_BIND_TYPE_HTTPS: return "h2,http/1.1"; default: return NULL; } } static void _ddr_extract_local_addresses(const struct sockaddr_storage *addr, unsigned char *ipv4_addr, int *ipv4_num, unsigned char *ipv6_addr, int *ipv6_num) { *ipv4_num = 0; *ipv6_num = 0; if (addr == NULL) { return; } switch (addr->ss_family) { case AF_INET: { const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr; memcpy(ipv4_addr, &addr_in->sin_addr.s_addr, DNS_RR_A_LEN); *ipv4_num = 1; } break; case AF_INET6: { const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr; memcpy(ipv6_addr, addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN); *ipv6_num = 1; } break; default: break; } } static int _ddr_build_svcb_record(struct dns_packet *packet, const char *domain, int ttl, int priority, const char *alpn, int port, unsigned char *ipv4_addr, int ipv4_num, unsigned char *ipv6_addr, int ipv6_num) { struct dns_rr_nested svcparam_buffer; if (dns_add_SVCB_start(&svcparam_buffer, packet, DNS_RRS_AN, domain, ttl, priority, NULL) != 0) { return -1; } /* Add ALPN parameter */ if (alpn != NULL) { uint8_t alpn_data[DNS_MAX_ALPN_LEN]; int alpn_data_len = encode_alpn_protos(alpn, alpn_data, sizeof(alpn_data)); if (alpn_data_len > 0) { dns_SVCB_add_alpn(&svcparam_buffer, alpn_data, alpn_data_len); } } /* Add port parameter */ if (port > 0) { dns_SVCB_add_port(&svcparam_buffer, port); } /* Add IPv4 hint */ if (ipv4_num > 0 && ipv4_addr != NULL) { unsigned char *ip_addr[1] = {ipv4_addr}; dns_SVCB_add_ipv4hint(&svcparam_buffer, ip_addr, ipv4_num); } /* Add IPv6 hint */ if (ipv6_num > 0 && ipv6_addr != NULL) { unsigned char *ip_addr[1] = {ipv6_addr}; dns_SVCB_add_ipv6hint(&svcparam_buffer, ip_addr, ipv6_num); } dns_add_SVCB_end(&svcparam_buffer); return 0; } int _dns_server_process_DDR(struct dns_request *request) { struct dns_server_post_context context; int ret = 0; int added_svcb = 0; int ttl = request->ip_ttl; _dns_server_post_context_init(&context, request); context.do_reply = 1; /* Initialize DNS response head */ struct dns_head head; memset(&head, 0, sizeof(head)); head.id = request->id; head.qr = DNS_QR_ANSWER; head.opcode = DNS_OP_QUERY; head.aa = 0; head.rd = 0; head.ra = 1; head.rcode = DNS_RC_NOERROR; /* Initialize DNS packet */ ret = dns_packet_init(context.packet, context.packet_maxlen, &head); if (ret != 0) { return _dns_server_reply_SOA(DNS_RC_NOERROR, request); } /* Add request domain */ ret = dns_add_domain(context.packet, request->domain, request->qtype, request->qclass); if (ret != 0) { return _dns_server_reply_SOA(DNS_RC_NOERROR, request); } /* Set default TTL if not set */ if (ttl <= 0) { ttl = 60; } /* Get local address for IP hints */ struct sockaddr_storage *local_addr = (struct sockaddr_storage *)dns_server_request_get_local_addr(request); /* Iterate through all bind IPs and create SVCB records for DDR-enabled bindings */ int priority = 1; for (int i = 0; i < dns_conf.bind_ip_num; i++) { struct dns_bind_ip *bind_ip = &dns_conf.bind_ip[i]; const char *alpn = NULL; int port = 0; char ip[DNS_MAX_IPLEN]; unsigned char ipv4_addr[DNS_RR_A_LEN]; int ipv4_num = 0; unsigned char ipv6_addr[DNS_RR_AAAA_LEN]; int ipv6_num = 0; /* Skip if DDR is not enabled for this binding */ if ((bind_ip->flags & BIND_FLAG_DDR) == 0) { continue; } /* Determine ALPN */ alpn = _ddr_get_alpn(bind_ip); if (alpn == NULL) { continue; } /* Extract port from IP string */ if (parse_ip(bind_ip->ip, ip, &port) != 0) { continue; } /* Extract local addresses for IP hints */ _ddr_extract_local_addresses(local_addr, ipv4_addr, &ipv4_num, ipv6_addr, &ipv6_num); /* Build SVCB record */ ret = _ddr_build_svcb_record(context.packet, request->domain, ttl, priority, alpn, port, ipv4_addr, ipv4_num, ipv6_addr, ipv6_num); if (ret == 0) { added_svcb++; priority++; } } /* If no SVCB records were added, return SOA */ if (added_svcb == 0) { return _dns_server_reply_SOA(DNS_RC_NOERROR, request); } /* Encode to binary data */ int encode_len = dns_encode(context.inpacket, context.inpacket_maxlen, context.packet); if (encode_len <= 0) { return _dns_server_reply_SOA(DNS_RC_NOERROR, request); } context.inpacket_len = encode_len; context.do_cache = 0; context.do_ipset = 0; _dns_server_reply_passthrough(&context); return 0; } ================================================ FILE: src/dns_server/ddr.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_DDR_H #define _DNS_SERVER_DDR_H #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_process_DDR(struct dns_request *request); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // _DNS_SERVER_DDR_H ================================================ FILE: src/dns_server/dns64.c ================================================ /************************************************************************* * * 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 . */ #include "dns64.h" #include "address.h" #include "context.h" #include "dns_server.h" #include "ptr.h" #include "request.h" #include "request_pending.h" #include "rules.h" #include "soa.h" #include "smartdns/dns_conf.h" #include #include int _dns_server_is_dns64_request(struct dns_request *request) { if (request->conf->dns_dns64.prefix_len <= 0) { return 0; } if (request->dualstack_selection_query == 1) { return 0; } if (strncmp(request->domain, DNS64_IPV4ONLY_APRA_DOMAIN, sizeof(DNS64_IPV4ONLY_APRA_DOMAIN)) == 0) { return 1; } if (request->qtype != DNS_T_AAAA) { return 0; } return 1; } static int _dns_server_process_ipv4only_arpa(struct dns_request *request) { if (strncmp(request->domain, DNS64_IPV4ONLY_APRA_DOMAIN, sizeof(DNS64_IPV4ONLY_APRA_DOMAIN)) != 0) { return -1; } /* address /domain/ rule */ switch (request->qtype) { case DNS_T_A: { u_int8_t *ipv4_addr1 = (u_int8_t[]){192, 0, 0, 170}; u_int8_t *ipv4_addr2 = (u_int8_t[]){192, 0, 0, 171}; memcpy(request->ip_addr, ipv4_addr1, DNS_RR_A_LEN); _dns_ip_address_check_add(request, request->cname, ipv4_addr1, DNS_T_A, 1, NULL); _dns_ip_address_check_add(request, request->cname, ipv4_addr2, DNS_T_A, 1, NULL); request->has_ip = 1; } break; case DNS_T_AAAA: /* no AAAA record for ipv4only.arpa */ request->has_ip = 0; break; default: goto errout; break; } request->rcode = DNS_RC_NOERROR; request->ip_ttl = _dns_server_get_local_ttl(request); request->dualstack_selection = 0; struct dns_server_post_context context; _dns_server_post_context_init(&context, request); context.do_reply = 1; context.do_audit = 1; context.do_ipset = 0; context.do_cache = 0; context.select_all_best_ip = 1; _dns_request_post(&context); _dns_server_reply_all_pending_list(request, &context); return 0; errout: return -1; } static enum DNS_CHILD_POST_RESULT _dns_server_process_dns64_callback(struct dns_request *request, struct dns_request *child_request, int is_first_resp) { unsigned long bucket = 0; struct dns_ip_address *addr_map = NULL; struct hlist_node *tmp = NULL; uint32_t key = 0; int addr_len = 0; if (request->has_ip == 1) { if (memcmp(request->ip_addr, request->conf->dns_dns64.prefix, 12) != 0) { return DNS_CHILD_POST_SKIP; } } if (child_request->qtype != DNS_T_A) { return DNS_CHILD_POST_FAIL; } if (child_request->has_cname == 1) { safe_strncpy(request->cname, child_request->cname, sizeof(request->cname)); request->has_cname = 1; request->ttl_cname = child_request->ttl_cname; } if (child_request->has_ip == 0 && request->has_ip == 0) { request->rcode = child_request->rcode; if (child_request->has_soa) { memcpy(&request->soa, &child_request->soa, sizeof(struct dns_soa)); request->has_soa = 1; return DNS_CHILD_POST_SKIP; } if (request->has_soa == 0) { _dns_server_setup_soa(request); request->has_soa = 1; } return DNS_CHILD_POST_FAIL; } if (request->has_ip == 0 && child_request->has_ip == 1) { request->rcode = child_request->rcode; memcpy(request->ip_addr, request->conf->dns_dns64.prefix, 12); memcpy(request->ip_addr + 12, child_request->ip_addr, 4); request->ip_ttl = child_request->ip_ttl; request->has_ip = 1; request->has_soa = 0; } pthread_mutex_lock(&request->ip_map_lock); hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node) { hash_del(&addr_map->node); free(addr_map); } pthread_mutex_unlock(&request->ip_map_lock); pthread_mutex_lock(&child_request->ip_map_lock); hash_for_each_safe(child_request->ip_map, bucket, tmp, addr_map, node) { struct dns_ip_address *new_addr_map = NULL; if (addr_map->addr_type == DNS_T_A) { addr_len = DNS_RR_A_LEN; } else { continue; } new_addr_map = zalloc(1, sizeof(struct dns_ip_address)); if (new_addr_map == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); pthread_mutex_unlock(&child_request->ip_map_lock); return DNS_CHILD_POST_FAIL; } new_addr_map->addr_type = DNS_T_AAAA; addr_len = DNS_RR_AAAA_LEN; memcpy(new_addr_map->ip_addr, request->conf->dns_dns64.prefix, 16); memcpy(new_addr_map->ip_addr + 12, addr_map->ip_addr, 4); new_addr_map->ping_time = addr_map->ping_time; key = jhash(new_addr_map->ip_addr, addr_len, 0); key = jhash(&new_addr_map->addr_type, sizeof(new_addr_map->addr_type), key); pthread_mutex_lock(&request->ip_map_lock); hash_add(request->ip_map, &new_addr_map->node, key); pthread_mutex_unlock(&request->ip_map_lock); } pthread_mutex_unlock(&child_request->ip_map_lock); if (request->dualstack_selection == 1) { return DNS_CHILD_POST_NO_RESPONSE; } return DNS_CHILD_POST_SKIP; } int _dns_server_process_dns64(struct dns_request *request) { if (_dns_server_is_dns64_request(request) == 0) { return 0; } tlog(TLOG_DEBUG, "query %s with dns64", request->domain); if (_dns_server_process_ipv4only_arpa(request) == 0) { return 2; } struct dns_request *child_request = _dns_server_new_child_request(request, request->domain, DNS_T_A, _dns_server_process_dns64_callback); if (child_request == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); return -1; } request->dualstack_selection = 0; child_request->prefetch_flags |= PREFETCH_FLAGS_NO_DUALSTACK; request->request_wait++; int ret = _dns_server_do_query(child_request, 0); if (ret != 0) { request->request_wait--; tlog(TLOG_ERROR, "do query %s type %d failed.\n", request->domain, request->qtype); goto errout; } _dns_server_request_release_complete(child_request, 0); return 0; errout: if (child_request) { request->child_request = NULL; _dns_server_request_release(child_request); } return -1; } ================================================ FILE: src/dns_server/dns64.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_DNS64_ #define _DNS_SERVER_DNS64_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_process_dns64(struct dns_request *request); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/dns_server.c ================================================ /************************************************************************* * * 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 . */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "dns_server.h" #include "address.h" #include "answer.h" #include "audit.h" #include "cache.h" #include "client_rule.h" #include "cname.h" #include "connection.h" #include "context.h" #include "dualstack.h" #include "ip_rule.h" #include "local_addr.h" #include "mdns.h" #include "neighbor.h" #include "ptr.h" #include "request.h" #include "request_pending.h" #include "rules.h" #include "server_https.h" #include "server_socket.h" #include "server_tcp.h" #include "server_tls.h" #include "server_udp.h" #include "server_http2.h" #include "soa.h" #include "speed_check.h" #include "smartdns/dns_cache.h" #include "smartdns/dns_client.h" #include "smartdns/dns_conf.h" #include "smartdns/dns_plugin.h" #include "smartdns/dns_stats.h" #include "smartdns/fast_ping.h" #include "smartdns/http_parse.h" #include "smartdns/lib/atomic.h" #include "smartdns/lib/hashtable.h" #include "smartdns/lib/list.h" #include "smartdns/lib/nftset.h" #include "smartdns/util.h" #include #include #include #include #include static int is_server_init; struct dns_server server; static void _dns_server_wakeup_thread(void) { uint64_t u = 1; int unused __attribute__((unused)); unused = write(server.event_fd, &u, sizeof(u)); } static int _dns_server_forward_request(unsigned char *inpacket, int inpacket_len) { return -1; } int _dns_reply_inpacket(struct dns_request *request, unsigned char *inpacket, int inpacket_len) { struct dns_server_conn_head *conn = request->conn; int ret = 0; if (conn == NULL) { tlog(TLOG_ERROR, "client is invalid, domain: %s", request->domain); return -1; } if (conn->type == DNS_CONN_TYPE_UDP_SERVER) { ret = _dns_server_reply_udp(request, (struct dns_server_conn_udp *)conn, inpacket, inpacket_len); } else if (conn->type == DNS_CONN_TYPE_TCP_CLIENT) { ret = _dns_server_reply_tcp(request, (struct dns_server_conn_tcp_client *)conn, inpacket, inpacket_len); } else if (conn->type == DNS_CONN_TYPE_TLS_CLIENT) { ret = _dns_server_reply_tcp(request, (struct dns_server_conn_tcp_client *)conn, inpacket, inpacket_len); } else if (conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) { ret = _dns_server_reply_https(request, (struct dns_server_conn_tcp_client *)conn, inpacket, inpacket_len); } else if (conn->type == DNS_CONN_TYPE_HTTP2_STREAM) { ret = _dns_server_reply_http2(request, (struct dns_server_conn_http2_stream *)conn, inpacket, inpacket_len); } else { ret = -1; } return ret; } static int _dns_server_resolve_callback_reply_passthrough(struct dns_request *request, const char *domain, struct dns_packet *packet, unsigned char *inpacket, int inpacket_len, unsigned int result_flag) { struct dns_server_post_context context; int ttl = 0; int ret = 0; ret = _dns_server_passthrough_rule_check(request, domain, packet, result_flag, &ttl); if (ret == 0) { return 0; } ttl = _dns_server_get_conf_ttl(request, ttl); _dns_server_post_context_init_from(&context, request, packet, inpacket, inpacket_len); context.do_cache = 1; context.do_audit = 1; context.do_reply = 1; context.do_ipset = 1; context.reply_ttl = ttl; return _dns_server_reply_passthrough(&context); } static int dns_server_resolve_callback(const char *domain, dns_result_type rtype, struct dns_server_info *server_info, struct dns_packet *packet, unsigned char *inpacket, int inpacket_len, void *user_ptr) { struct dns_request *request = user_ptr; int ret = 0; int need_passthrouh = 0; unsigned long result_flag = dns_client_server_result_flag(server_info); if (request == NULL) { return -1; } if (rtype == DNS_QUERY_RESULT) { tlog(TLOG_DEBUG, "query result from server %s:%d, type: %d, domain: %s qtype: %d rcode: %d, id: %d", dns_client_get_server_ip(server_info), dns_client_get_server_port(server_info), dns_client_get_server_type(server_info), domain, request->qtype, packet->head.rcode, request->id); if (request->passthrough == 1 && atomic_read(&request->notified) == 0) { return _dns_server_resolve_callback_reply_passthrough(request, domain, packet, inpacket, inpacket_len, result_flag); } if (request->prefetch == 0 && request->response_mode == DNS_RESPONSE_MODE_FASTEST_RESPONSE && atomic_read(&request->notified) == 0) { struct dns_server_post_context context; int ttl = 0; ret = _dns_server_passthrough_rule_check(request, domain, packet, result_flag, &ttl); if (ret != 0) { _dns_server_post_context_init_from(&context, request, packet, inpacket, inpacket_len); context.do_cache = 1; context.do_audit = 1; context.do_reply = 1; context.do_ipset = 1; context.reply_ttl = _dns_server_get_reply_ttl(request, ttl); context.cache_ttl = _dns_server_get_conf_ttl(request, ttl); request->ip_ttl = context.cache_ttl; _dns_server_reply_passthrough(&context); request->cname[0] = 0; request->has_ip = 0; request->has_cname = 0; request->has_ping_result = 0; request->has_soa = 0; request->has_ptr = 0; request->ping_time = -1; request->ip_ttl = 0; } } ret = _dns_server_process_answer(request, domain, packet, result_flag, &need_passthrouh); if (ret == 0 && need_passthrouh == 1 && atomic_read(&request->notified) == 0) { /* not supported record, passthrouth */ request->passthrough = 1; return _dns_server_resolve_callback_reply_passthrough(request, domain, packet, inpacket, inpacket_len, result_flag); } _dns_server_passthrough_may_complete(request); return ret; } else if (rtype == DNS_QUERY_ERR) { tlog(TLOG_ERROR, "request failed, %s", domain); return -1; } else { _dns_server_query_end(request); } return 0; } int dns_server_get_server_name(char *name, int name_len) { if (name == NULL || name_len <= 0) { return -1; } if (dns_conf.server_name[0] == 0) { char hostname[DNS_MAX_CNAME_LEN]; char domainname[DNS_MAX_CNAME_LEN]; /* get local domain name */ if (getdomainname(domainname, DNS_MAX_CNAME_LEN - 1) == 0) { /* check domain is valid */ if (strncmp(domainname, "(none)", DNS_MAX_CNAME_LEN - 1) == 0) { domainname[0] = '\0'; } } if (gethostname(hostname, DNS_MAX_CNAME_LEN - 1) == 0) { /* check hostname is valid */ if (strncmp(hostname, "(none)", DNS_MAX_CNAME_LEN - 1) == 0) { hostname[0] = '\0'; } } if (hostname[0] != '\0' && domainname[0] != '\0') { snprintf(name, name_len, "%.64s.%.128s", hostname, domainname); } else if (hostname[0] != '\0') { safe_strncpy(name, hostname, name_len); } else { safe_strncpy(name, "smartdns", name_len); } } else { /* return configured server name */ safe_strncpy(name, dns_conf.server_name, name_len); } return 0; } int _dns_server_do_query(struct dns_request *request, int skip_notify_event) { int ret = -1; const char *server_group_name = NULL; struct dns_query_options options; char *request_domain = request->domain; char domain_buffer[DNS_MAX_CNAME_LEN * 2]; request->send_tick = get_tick_count(); if (_dns_server_setup_request_conf_pre(request) != 0) { goto errout; } /* lookup domain rule */ _dns_server_get_domain_rule(request); _dns_server_setup_dns_group_name(request, &server_group_name); if (_dns_server_setup_request_conf(request) != 0) { goto errout; } if (_dns_server_mdns_query_setup(request, server_group_name, &request_domain, domain_buffer, sizeof(domain_buffer)) != 0) { goto errout; } if (_dns_server_process_cname_pre(request) != 0) { goto errout; } _dns_server_set_dualstack_selection(request); if (_dns_server_process_special_query(request) == 0) { goto clean_exit; } if (_dns_server_pre_process_server_flags(request) == 0) { goto clean_exit; } /* process domain flag */ if (_dns_server_pre_process_rule_flags(request) == 0) { goto clean_exit; } /* process domain address */ if (_dns_server_process_address(request) == 0) { goto clean_exit; } if (_dns_server_process_https_svcb(request) != 0) { goto clean_exit; } if (_dns_server_process_smartdns_domain(request) == 0) { goto clean_exit; } if (_dns_server_process_host(request) == 0) { goto clean_exit; } /* process qtype soa */ if (_dns_server_qtype_soa(request) == 0) { goto clean_exit; } /* process speed check rule */ _dns_server_process_speed_rule(request); /* check and set passthrough */ _dns_server_check_set_passthrough(request); /* process ptr */ if (_dns_server_process_ptr_query(request) == 0) { goto clean_exit; } /* process cache */ if (request->prefetch == 0 && request->dualstack_selection_query == 0) { _dns_server_mdns_query_setup_server_group(request, &server_group_name); if (_dns_server_process_cache(request) == 0) { goto clean_exit; } } ret = _dns_server_set_to_pending_list(request); if (ret == 0) { goto clean_exit; } if (_dns_server_process_cname(request) != 0) { goto clean_exit; } // setup options _dns_server_setup_query_option(request, &options); _dns_server_mdns_query_setup_server_group(request, &server_group_name); pthread_mutex_lock(&server.request_list_lock); if (list_empty(&server.request_list) && skip_notify_event == 1) { _dns_server_wakeup_thread(); } list_add_tail(&request->list, &server.request_list); pthread_mutex_unlock(&server.request_list_lock); ret = _dns_server_process_dns64(request); if (ret != 0) { if (ret == 2) { /* dns64 processing, return success */ goto clean_exit; } goto errout; } // Get reference for DNS query request->request_wait++; _dns_server_request_get(request); if (dns_client_query(request_domain, request->qtype, dns_server_resolve_callback, request, server_group_name, &options) != 0) { request->request_wait--; _dns_server_request_release(request); tlog(TLOG_DEBUG, "send dns request failed."); goto errout; } /* When the dual stack ip preference is enabled, both A and AAAA records are requested. */ _dns_server_query_dualstack(request); clean_exit: return 0; errout: return ret; } static int _dns_server_reply_format_error(struct dns_request *request, struct dns_server_conn_head *conn, unsigned char *inpacket, int inpacket_len, struct sockaddr_storage *local, socklen_t local_len, struct sockaddr_storage *from, socklen_t from_len) { unsigned char packet_buff[DNS_PACKSIZE]; struct dns_packet *packet = (struct dns_packet *)packet_buff; int decode_len = 0; int need_release = 0; int ret = -1; if (request == NULL) { decode_len = dns_decode_head_only(packet, DNS_PACKSIZE, inpacket, inpacket_len); if (decode_len < 0) { ret = -1; goto out; } request = _dns_server_new_request(); if (request == NULL) { ret = -1; goto out; } need_release = 1; memcpy(&request->localaddr, local, local_len); _dns_server_request_set_client(request, conn); _dns_server_request_set_client_addr(request, from, from_len); _dns_server_request_set_id(request, packet->head.id); } request->rcode = DNS_RC_FORMERR; request->no_cache = 1; request->send_tick = get_tick_count(); ret = 0; out: if (request && need_release) { _dns_server_request_release(request); } return ret; } int _dns_server_recv(struct dns_server_conn_head *conn, unsigned char *inpacket, int inpacket_len, struct sockaddr_storage *local, socklen_t local_len, struct sockaddr_storage *from, socklen_t from_len) { int decode_len = 0; int ret = -1; unsigned char packet_buff[DNS_PACKSIZE]; char name[DNS_MAX_CNAME_LEN]; struct dns_packet *packet = (struct dns_packet *)packet_buff; struct dns_request *request = NULL; struct dns_client_rules *client_rules = NULL; /* decode packet */ tlog(TLOG_DEBUG, "recv query packet from %s, len = %d, type = %d", get_host_by_addr(name, sizeof(name), (struct sockaddr *)from), inpacket_len, conn->type); decode_len = dns_decode(packet, DNS_PACKSIZE, inpacket, inpacket_len); if (decode_len < 0) { tlog(TLOG_DEBUG, "decode failed.\n"); ret = RECV_ERROR_INVALID_PACKET; if (dns_conf.dns_save_fail_packet) { dns_packet_save(dns_conf.dns_save_fail_packet_dir, "server", name, inpacket, inpacket_len); } goto errout; } if (smartdns_plugin_func_server_recv(packet, inpacket, inpacket_len, local, local_len, from, from_len) != 0) { return 0; } tlog(TLOG_DEBUG, "request qdcount = %d, ancount = %d, nscount = %d, nrcount = %d, len = %d, id = %d, tc = %d, rd = %d, " "ra = " "%d, rcode = %d\n", packet->head.qdcount, packet->head.ancount, packet->head.nscount, packet->head.nrcount, inpacket_len, packet->head.id, packet->head.tc, packet->head.rd, packet->head.ra, packet->head.rcode); client_rules = _dns_server_get_client_rules(from, from_len); request = _dns_server_new_request(); if (request == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); goto errout; } memcpy(&request->localaddr, local, local_len); _dns_server_request_set_mac(request, from, from_len); _dns_server_request_set_client(request, conn); _dns_server_request_set_client_addr(request, from, from_len); _dns_server_request_set_id(request, packet->head.id); stats_inc(&dns_stats.request.from_client_count); if (_dns_server_parser_request(request, packet) != 0) { tlog(TLOG_DEBUG, "parser request failed."); ret = RECV_ERROR_INVALID_PACKET; goto errout; } tlog(TLOG_DEBUG, "query %s from %s, qtype: %d, id: %d, query-num: %ld", request->domain, name, request->qtype, request->id, atomic_read(&server.request_num)); if (atomic_read(&server.request_num) > dns_conf.max_query_limit && dns_conf.max_query_limit > 0) { static time_t last_log_time = 0; time_t now = time(NULL); if (now - last_log_time > 120) { last_log_time = now; tlog(TLOG_WARN, "maximum number of dns queries reached, max: %d", dns_conf.max_query_limit); } request->rcode = DNS_RC_REFUSED; ret = 0; goto errout; } ret = _dns_server_request_set_client_rules(request, client_rules); if (ret != 0) { ret = 0; goto errout; } ret = _dns_server_do_query(request, 1); if (ret != 0) { tlog(TLOG_DEBUG, "do query %s failed.\n", request->domain); goto errout; } _dns_server_request_release_complete(request, 0); return ret; errout: if (ret == RECV_ERROR_INVALID_PACKET) { if (_dns_server_reply_format_error(request, conn, inpacket, inpacket_len, local, local_len, from, from_len) == 0) { ret = 0; } } if (request) { request->send_tick = get_tick_count(); request->no_cache = 1; _dns_server_forward_request(inpacket, inpacket_len); _dns_server_request_release(request); } return ret; } int dns_server_query(const char *domain, int qtype, struct dns_server_query_option *server_query_option, dns_result_callback callback, void *user_ptr) { int ret = -1; struct dns_request *request = NULL; request = _dns_server_new_request(); if (request == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); goto errout; } safe_strncpy(request->domain, domain, sizeof(request->domain)); request->qtype = qtype; _dns_server_setup_server_query_options(request, server_query_option); _dns_server_request_set_callback(request, callback, user_ptr); ret = _dns_server_do_query(request, 0); if (ret != 0) { tlog(TLOG_DEBUG, "do query %s failed.\n", domain); goto errout; } _dns_server_request_release_complete(request, 0); return ret; errout: if (request) { _dns_server_request_set_callback(request, NULL, NULL); _dns_server_request_release(request); } return ret; } static int _dns_server_process(struct dns_server_conn_head *conn, struct epoll_event *event, unsigned long now) { int ret = 0; _dns_server_client_touch(conn); _dns_server_conn_get(conn); if (conn->type == DNS_CONN_TYPE_UDP_SERVER) { struct dns_server_conn_udp *udpconn = (struct dns_server_conn_udp *)conn; ret = _dns_server_process_udp(udpconn, event, now); } else if (conn->type == DNS_CONN_TYPE_TCP_SERVER) { struct dns_server_conn_tcp_server *tcpserver = (struct dns_server_conn_tcp_server *)conn; ret = _dns_server_tcp_accept(tcpserver, event, now); } else if (conn->type == DNS_CONN_TYPE_TCP_CLIENT) { struct dns_server_conn_tcp_client *tcpclient = (struct dns_server_conn_tcp_client *)conn; ret = _dns_server_process_tcp(tcpclient, event, now); if (ret != 0) { char name[DNS_MAX_CNAME_LEN]; tlog(TLOG_DEBUG, "process TCP packet from %s failed.", get_host_by_addr(name, sizeof(name), (struct sockaddr *)&tcpclient->addr)); } } else if (conn->type == DNS_CONN_TYPE_TLS_SERVER || conn->type == DNS_CONN_TYPE_HTTPS_SERVER) { struct dns_server_conn_tls_server *tls_server = (struct dns_server_conn_tls_server *)conn; ret = _dns_server_tls_accept(tls_server, event, now); } else if (conn->type == DNS_CONN_TYPE_TLS_CLIENT || conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) { struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)conn; ret = _dns_server_process_tls(tls_client, event, now); if (ret != 0) { char name[DNS_MAX_CNAME_LEN]; tlog(TLOG_DEBUG, "process TLS packet from %s failed.", get_host_by_addr(name, sizeof(name), (struct sockaddr *)&tls_client->tcp.addr)); } } else { tlog(TLOG_ERROR, "unsupported dns server type %d", conn->type); _dns_server_client_close(conn); ret = -1; } _dns_server_conn_release(conn); if (ret == RECV_ERROR_INVALID_PACKET) { ret = 0; } return ret; } static int _dns_server_socket(void) { int i = 0; for (i = 0; i < dns_conf.bind_ip_num; i++) { struct dns_bind_ip *bind_ip = &dns_conf.bind_ip[i]; tlog(TLOG_INFO, "bind ip %s, type %d", bind_ip->ip, bind_ip->type); switch (bind_ip->type) { case DNS_BIND_TYPE_UDP: if (_dns_server_socket_udp(bind_ip) != 0) { goto errout; } break; case DNS_BIND_TYPE_TCP: if (_dns_server_socket_tcp(bind_ip) != 0) { goto errout; } break; case DNS_BIND_TYPE_HTTPS: if (_dns_server_socket_tls(bind_ip, DNS_CONN_TYPE_HTTPS_SERVER) != 0) { goto errout; } break; case DNS_BIND_TYPE_TLS: if (_dns_server_socket_tls(bind_ip, DNS_CONN_TYPE_TLS_SERVER) != 0) { goto errout; } break; default: break; } } return 0; errout: return -1; } #ifdef TEST static void _dns_server_check_need_exit(void) { static int parent_pid = 0; if (parent_pid == 0) { parent_pid = getppid(); } if (parent_pid != getppid()) { tlog(TLOG_WARN, "parent process exit, exit too."); dns_server_stop(); } } #else #define _dns_server_check_need_exit() #endif static void _dns_server_period_run_second(void) { static unsigned int sec = 0; sec++; _dns_server_tcp_idle_check(); _dns_server_check_need_exit(); if (sec % IPV6_READY_CHECK_TIME == 0 && is_ipv6_ready == 0) { dns_server_check_ipv6_ready(); } if (sec % 60 == 0) { if (dns_server_check_update_hosts() == 0) { tlog(TLOG_INFO, "Update host file data"); } } _dns_server_save_cache_to_file(); dns_stats_period_run_second(); } static void _dns_server_period_run(unsigned int msec) { struct dns_request *request = NULL; struct dns_request *tmp = NULL; LIST_HEAD(check_list); if ((msec % 10) == 0) { _dns_server_period_run_second(); } unsigned long now = get_tick_count(); pthread_mutex_lock(&server.request_list_lock); list_for_each_entry_safe(request, tmp, &server.request_list, list) { /* Need to use tcping detection speed */ int check_order = request->check_order + 1; if (atomic_read(&request->ip_map_num) == 0 || request->has_soa) { continue; } if ((request->send_tick < now - (check_order * DNS_PING_CHECK_INTERVAL) && request->has_ping_result == 0) || request->ping_time > DNS_PING_RTT_CHECK_THRESHOLD) { _dns_server_request_get(request); list_add_tail(&request->check_list, &check_list); request->check_order++; } } pthread_mutex_unlock(&server.request_list_lock); list_for_each_entry_safe(request, tmp, &check_list, check_list) { _dns_server_second_ping_check(request); list_del_init(&request->check_list); _dns_server_request_release(request); } } int dns_server_run(void) { struct epoll_event events[DNS_MAX_EVENTS + 1]; int num = 0; int i = 0; unsigned long now = {0}; unsigned int msec = 0; int sleep = 100; int sleep_time = 0; unsigned long expect_time = 0; unsigned long start_time = 0; now = get_tick_count(); start_time = now; expect_time = now + sleep; while (atomic_read(&server.run)) { now = get_tick_count(); if (now >= expect_time) { unsigned long elapsed_from_start = now - start_time; unsigned int current_period = (elapsed_from_start + sleep / 2) / sleep; if (current_period > msec) { msec = current_period; } expect_time = start_time + (msec + 1) * sleep; _dns_server_period_run(msec); msec++; /* When server is idle, the sleep time is 1000ms, to reduce CPU usage */ pthread_mutex_lock(&server.request_list_lock); if (list_empty(&server.request_list)) { if (msec % 10 != 0) { msec = ((msec / 10) + 1) * 10; expect_time = start_time + msec * sleep; } } pthread_mutex_unlock(&server.request_list_lock); } sleep_time = (int)(expect_time - now); if (sleep_time < 0) { sleep_time = 0; } num = epoll_wait(server.epoll_fd, events, DNS_MAX_EVENTS, sleep_time); if (num < 0) { usleep(100000); continue; } if (num == 0) { continue; } for (i = 0; i < num; i++) { struct epoll_event *event = &events[i]; /* read event */ if (unlikely(event->data.fd == server.event_fd)) { uint64_t value; int unused __attribute__((unused)); unused = read(server.event_fd, &value, sizeof(uint64_t)); continue; } if (unlikely(event->data.fd == server.local_addr_cache.fd_netlink)) { _dns_server_process_local_addr_cache(event->data.fd, event, now); continue; } struct dns_server_conn_head *conn_head = event->data.ptr; if (conn_head == NULL) { tlog(TLOG_ERROR, "invalid fd\n"); continue; } if (_dns_server_process(conn_head, event, now) != 0) { tlog(TLOG_DEBUG, "dns server process failed."); } } } _dns_server_close_socket_server(); close(server.epoll_fd); server.epoll_fd = -1; return 0; } int dns_server_start(void) { struct dns_server_conn_head *conn = NULL; list_for_each_entry(conn, &server.conn_list, list) { if (conn->fd <= 0) { continue; } if (_dns_server_epoll_ctl(conn, EPOLL_CTL_ADD, EPOLLIN) != 0) { tlog(TLOG_ERROR, "epoll ctl failed."); return -1; } } return 0; } static int _dns_server_init_wakeup_event(void) { int fdevent = -1; fdevent = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (fdevent < 0) { tlog(TLOG_ERROR, "create eventfd failed, %s\n", strerror(errno)); goto errout; } struct epoll_event event; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLERR; event.data.fd = fdevent; if (epoll_ctl(server.epoll_fd, EPOLL_CTL_ADD, fdevent, &event) != 0) { tlog(TLOG_ERROR, "set eventfd failed, %s\n", strerror(errno)); goto errout; } server.event_fd = fdevent; return 0; errout: return -1; } int dns_server_init(void) { pthread_mutexattr_t attr; int epollfd = -1; int ret = -1; _dns_server_check_need_exit(); if (is_server_init == 1) { return -1; } if (server.epoll_fd > 0) { return -1; } if (_dns_server_audit_init() != 0) { tlog(TLOG_ERROR, "init audit failed."); goto errout; } memset(&server, 0, sizeof(server)); pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); INIT_LIST_HEAD(&server.conn_list); time(&server.cache_save_time); atomic_set(&server.request_num, 0); pthread_mutex_init(&server.request_list_lock, NULL); pthread_mutex_init(&server.conn_list_lock, &attr); INIT_LIST_HEAD(&server.request_list); pthread_mutexattr_destroy(&attr); epollfd = epoll_create1(EPOLL_CLOEXEC); if (epollfd < 0) { tlog(TLOG_ERROR, "create epoll failed, %s\n", strerror(errno)); goto errout; } ret = _dns_server_socket(); if (ret != 0) { tlog(TLOG_ERROR, "create server socket failed.\n"); goto errout; } server.epoll_fd = epollfd; atomic_set(&server.run, 1); if (dns_server_start() != 0) { tlog(TLOG_ERROR, "start service failed.\n"); goto errout; } dns_server_check_ipv6_ready(); tlog(TLOG_INFO, "%s", (is_ipv6_ready) ? "IPV6 is ready, enable IPV6 features" : "IPV6 is not ready or speed check is disabled, disable IPV6 features"); if (_dns_server_init_wakeup_event() != 0) { tlog(TLOG_ERROR, "init wakeup event failed."); goto errout; } if (_dns_server_cache_init() != 0) { tlog(TLOG_ERROR, "init dns cache filed."); goto errout; } if (_dns_server_local_addr_cache_init() != 0) { tlog(TLOG_WARN, "init local addr cache failed, disable local ptr."); dns_conf.local_ptr_enable = 0; } if (_dns_server_neighbor_cache_init() != 0) { tlog(TLOG_ERROR, "init neighbor cache failed."); goto errout; } is_server_init = 1; return 0; errout: atomic_set(&server.run, 0); if (epollfd) { close(epollfd); } _dns_server_close_socket(); pthread_mutex_destroy(&server.request_list_lock); return -1; } void dns_server_stop(void) { atomic_set(&server.run, 0); _dns_server_wakeup_thread(); } void dns_server_exit(void) { if (is_server_init == 0) { return; } if (server.event_fd > 0) { close(server.event_fd); server.event_fd = -1; } if (server.cache_save_pid > 0) { kill(server.cache_save_pid, SIGKILL); server.cache_save_pid = 0; } _dns_server_close_socket(); _dns_server_local_addr_cache_destroy(); _dns_server_neighbor_cache_remove_all(); _dns_server_cache_save(0); _dns_server_request_remove_all(); pthread_mutex_destroy(&server.request_list_lock); dns_cache_destroy(); is_server_init = 0; } ================================================ FILE: src/dns_server/dns_server.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_H_ #define _DNS_SERVER_H_ #include "smartdns/lib/atomic.h" #include "smartdns/lib/hashtable.h" #include "smartdns/lib/list.h" #include "smartdns/dns.h" #include "smartdns/dns_conf.h" #include "smartdns/dns_server.h" #include "smartdns/http2.h" #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ #define DNS_MAX_EVENTS 256 #define IPV6_READY_CHECK_TIME 180 #define DNS_SERVER_TMOUT_TTL (5 * 60) #define DNS_SERVER_FAIL_TTL (3) #define DNS_SERVER_SOA_TTL (30) #define DNS_SERVER_ADDR_TTL (60) #define DNS_CONN_BUFF_SIZE 4096 #define DNS_REQUEST_MAX_TIMEOUT 950 #define DNS_PING_TIMEOUT (DNS_REQUEST_MAX_TIMEOUT) #define DNS_PING_CHECK_INTERVAL (100) #define DNS_PING_RTT_CHECK_THRESHOLD (100 * 10) #define DNS_PING_SECOND_TIMEOUT (DNS_REQUEST_MAX_TIMEOUT - DNS_PING_CHECK_INTERVAL) #define SOCKET_IP_TOS (IPTOS_LOWDELAY | IPTOS_RELIABILITY) #define SOCKET_PRIORITY (6) #define CACHE_AUTO_ENABLE_SIZE (1024 * 1024 * 128) #define EXPIRED_DOMAIN_PREFETCH_TIME (3600 * 8) #define DNS_MAX_DOMAIN_REFETCH_NUM 64 #define DNS_SERVER_NEIGHBOR_CACHE_TIMEOUT 1200 #define DNS_SERVER_NEIGHBOR_CACHE_NOMAC_TIMEOUT 60 #define DNS_SERVER_NEIGHBOR_CACHE_MAX_NUM (1024 * 16) #define PREFETCH_FLAGS_NO_DUALSTACK (1 << 0) #define PREFETCH_FLAGS_EXPIRED (1 << 1) #define PREFETCH_FLAGS_NOPREFETCH (1 << 2) #define RECV_ERROR_AGAIN 1 #define RECV_ERROR_OK 0 #define RECV_ERROR_FAIL (-1) #define RECV_ERROR_CLOSE (-2) #define RECV_ERROR_INVALID_PACKET (-3) #define RECV_ERROR_BAD_PATH (-4) typedef enum { DNS_CONN_TYPE_UDP_SERVER = 0, DNS_CONN_TYPE_TCP_SERVER, DNS_CONN_TYPE_TCP_CLIENT, DNS_CONN_TYPE_TLS_SERVER, DNS_CONN_TYPE_TLS_CLIENT, DNS_CONN_TYPE_HTTPS_SERVER, DNS_CONN_TYPE_HTTPS_CLIENT, DNS_CONN_TYPE_HTTP2_STREAM, } DNS_CONN_TYPE; typedef enum DNS_CHILD_POST_RESULT { DNS_CHILD_POST_SUCCESS = 0, DNS_CHILD_POST_FAIL, DNS_CHILD_POST_SKIP, DNS_CHILD_POST_NO_RESPONSE, } DNS_CHILD_POST_RESULT; struct rule_walk_args { void *args; int rule_index; uint32_t full_key_len; unsigned char *key[DOMAIN_RULE_MAX]; uint32_t key_len[DOMAIN_RULE_MAX]; }; struct neighbor_enum_args { uint8_t *netaddr; int netaddr_len; struct client_roue_group_mac *group_mac; }; struct neighbor_cache_item { struct hlist_node node; struct list_head list; unsigned char ip_addr[DNS_RR_AAAA_LEN]; int ip_addr_len; unsigned char mac[6]; int has_mac; time_t last_update_time; }; struct neighbor_cache { DECLARE_HASHTABLE(cache, 6); atomic_t cache_num; struct list_head list; pthread_mutex_t lock; }; struct local_addr_cache_item { unsigned char ip_addr[DNS_RR_AAAA_LEN]; int ip_addr_len; int mask_len; }; struct local_addr_cache { radix_tree_t *addr; int fd_netlink; }; struct dns_conn_buf { uint8_t buf[DNS_CONN_BUFF_SIZE]; int buffsize; int size; }; struct dns_server_conn_head { DNS_CONN_TYPE type; int fd; struct list_head list; time_t last_request_time; atomic_t refcnt; const char *dns_group; uint32_t server_flags; struct nftset_ipset_rules *ipset_nftset_rule; }; struct dns_server_post_context { unsigned char inpacket_buff[DNS_IN_PACKSIZE]; unsigned char *inpacket; int inpacket_maxlen; int inpacket_len; unsigned char packet_buff[DNS_PACKSIZE]; unsigned int packet_maxlen; struct dns_request *request; struct dns_packet *packet; int ip_num; const unsigned char *ip_addr[MAX_IP_NUM]; dns_type_t qtype; int do_cache; int do_reply; int do_ipset; int do_log_result; int reply_ttl; int cache_ttl; int no_check_add_ip; int do_audit; int do_force_soa; int skip_notify_count; int select_all_best_ip; int no_release_parent; int is_cache_reply; }; typedef enum dns_server_client_status { DNS_SERVER_CLIENT_STATUS_INIT = 0, DNS_SERVER_CLIENT_STATUS_CONNECTING, DNS_SERVER_CLIENT_STATUS_CONNECTIONLESS, DNS_SERVER_CLIENT_STATUS_CONNECTED, DNS_SERVER_CLIENT_STATUS_DISCONNECTED, } dns_server_client_status; struct dns_server_conn_udp { struct dns_server_conn_head head; socklen_t addr_len; struct sockaddr_storage addr; }; struct dns_server_conn_tcp_server { struct dns_server_conn_head head; }; struct dns_server_conn_tls_server { struct dns_server_conn_head head; SSL_CTX *ssl_ctx; }; struct dns_server_conn_tcp_client { struct dns_server_conn_head head; struct dns_conn_buf recvbuff; struct dns_conn_buf sndbuff; socklen_t addr_len; struct sockaddr_storage addr; socklen_t localaddr_len; struct sockaddr_storage localaddr; int conn_idle_timeout; dns_server_client_status status; }; struct dns_server_conn_tls_client { struct dns_server_conn_tcp_client tcp; SSL *ssl; int ssl_want_write; pthread_mutex_t ssl_lock; void *http2_ctx; char alpn_selected[32]; }; /* ip address lists of domain */ struct dns_ip_address { struct hlist_node node; int hitnum; unsigned long recv_tick; int ping_time; dns_type_t addr_type; char cname[DNS_MAX_CNAME_LEN]; unsigned char ip_addr[DNS_RR_AAAA_LEN]; }; struct dns_request_pending_list { pthread_mutex_t request_list_lock; unsigned short qtype; char domain[DNS_MAX_CNAME_LEN]; uint32_t server_flags; char dns_group_name[DNS_GROUP_NAME_LEN]; struct list_head request_list; struct hlist_node node; }; struct dns_request_domain_rule { uint32_t flags; struct dns_rule *rules[DOMAIN_RULE_MAX]; int is_sub_rule[DOMAIN_RULE_MAX]; }; typedef DNS_CHILD_POST_RESULT (*child_request_callback)(struct dns_request *request, struct dns_request *child_request, int is_first_resp); struct dns_request_https { struct list_head list; char domain[DNS_MAX_CNAME_LEN]; char target[DNS_MAX_CNAME_LEN]; int ttl; int priority; char alpn[DNS_MAX_ALPN_LEN]; int alpn_len; int port; char ech[DNS_MAX_ECH_LEN]; int ech_len; int has_ipv4; unsigned char ipv4_addr[DNS_RR_A_LEN]; int has_ipv6; unsigned char ipv6_addr[DNS_RR_AAAA_LEN]; }; struct dns_request_srv { struct list_head list; char host[DNS_MAX_CNAME_LEN]; unsigned short priority; unsigned short weight; unsigned short port; }; struct dns_request { atomic_t refcnt; struct dns_server_conn_head *conn; struct dns_conf_group *conf; uint32_t server_flags; char dns_group_name[DNS_GROUP_NAME_LEN]; /* dns request list */ struct list_head list; struct list_head pending_list; /* dns request timeout check list */ struct list_head check_list; /* dns query */ char domain[DNS_MAX_CNAME_LEN]; char *original_domain; dns_type_t qtype; int qclass; unsigned long send_tick; unsigned short id; unsigned short rcode; unsigned short ss_family; char remote_server_fail; char skip_qtype_soa; union { struct sockaddr_in in; struct sockaddr_in6 in6; struct sockaddr addr; }; socklen_t addr_len; struct sockaddr_storage localaddr; uint8_t mac[6]; int has_ecs; struct dns_opt_ecs ecs; int edns0_do; struct list_head https_svcb_list; dns_result_callback result_callback; void *user_ptr; int has_ping_result; int has_ping_tcp; int has_ptr; char ptr_hostname[DNS_MAX_CNAME_LEN]; int has_cname; char cname[DNS_MAX_CNAME_LEN]; int ttl_cname; int has_ip; int ping_time; int ip_ttl; unsigned char ip_addr[DNS_RR_AAAA_LEN]; int ip_addr_type; struct dns_soa soa; int has_soa; int force_soa; int is_mdns_lookup; int is_cache_reply; struct list_head srv_list; atomic_t notified; atomic_t do_callback; atomic_t adblock; atomic_t soa_num; atomic_t plugin_complete_called; /* send original raw packet to server/client like proxy */ /* 0: not passthrough, reply to client 1: passthrough, reply to client, no modify packet 2: passthrough, reply to client, check and filter ip addresses. */ int passthrough; int request_wait; int prefetch; int prefetch_flags; int dualstack_selection; int dualstack_selection_force_soa; int dualstack_selection_query; int dualstack_selection_ping_time; int dualstack_selection_has_ip; struct dns_request *dualstack_request; int no_serve_expired; pthread_mutex_t ip_map_lock; struct dns_request *child_request; struct dns_request *parent_request; child_request_callback child_callback; atomic_t ip_map_num; DECLARE_HASHTABLE(ip_map, 4); struct dns_request_domain_rule domain_rule; int skip_domain_rule; const struct dns_domain_check_orders *check_order_list; int check_order; enum response_mode_type response_mode; struct dns_request_pending_list *request_pending_list; int no_select_possible_ip; int no_cache_cname; int no_cache; int no_ipalias; int has_cname_loop; void *private_data; uint64_t query_timestamp; int query_time; }; /* dns server data */ struct dns_server { atomic_t run; int epoll_fd; int event_fd; struct list_head conn_list; pthread_mutex_t conn_list_lock; pid_t cache_save_pid; time_t cache_save_time; /* dns request list */ pthread_mutex_t request_list_lock; struct list_head request_list; atomic_t request_num; DECLARE_HASHTABLE(request_pending, 4); pthread_mutex_t request_pending_lock; int update_neighbor_cache; struct neighbor_cache neighbor_cache; struct local_addr_cache local_addr_cache; }; extern struct dns_server server; int _dns_server_recv(struct dns_server_conn_head *conn, unsigned char *inpacket, int inpacket_len, struct sockaddr_storage *local, socklen_t local_len, struct sockaddr_storage *from, socklen_t from_len); int _dns_reply_inpacket(struct dns_request *request, unsigned char *inpacket, int inpacket_len); int _dns_server_do_query(struct dns_request *request, int skip_notify_event); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/dualstack.c ================================================ /************************************************************************* * * 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 . */ #include "dualstack.h" #include "dns_server.h" #include "request.h" #include "rules.h" #include "smartdns/fast_ping.h" #include #include int is_ipv6_ready; int dns_is_ipv6_ready(void) { return is_ipv6_ready; } void dns_server_check_ipv6_ready(void) { static int do_get_conf = 0; static int is_icmp_check_set; static int is_tcp_check_set; static int is_tcp_syn_check_set; if (do_get_conf == 0) { if (dns_conf.has_icmp_check == 1) { is_icmp_check_set = 1; } if (dns_conf.has_tcp_check == 1) { is_tcp_check_set = 1; } if (dns_conf.has_tcp_syn_check == 1) { is_tcp_syn_check_set = 1; } if (is_icmp_check_set == 0) { tlog(TLOG_INFO, "ICMP ping is disabled, no ipv6 icmp check feature"); } if (is_tcp_syn_check_set == 0) { tlog(TLOG_INFO, "TCP-SYN ping is disabled, no ipv6 tcp-syn check feature"); } do_get_conf = 1; } if (is_icmp_check_set) { struct ping_host_struct *check_ping = fast_ping_start(PING_TYPE_ICMP, "2001::", 1, 0, 100, NULL, NULL); if (check_ping) { fast_ping_stop(check_ping); is_ipv6_ready = 1; return; } if (errno == EADDRNOTAVAIL) { is_ipv6_ready = 0; return; } } if (is_tcp_check_set) { struct ping_host_struct *check_ping = fast_ping_start(PING_TYPE_TCP, "2001::", 1, 0, 100, NULL, NULL); if (check_ping) { fast_ping_stop(check_ping); is_ipv6_ready = 1; return; } if (errno == EADDRNOTAVAIL) { is_ipv6_ready = 0; return; } } if (is_tcp_syn_check_set) { struct ping_host_struct *check_ping = fast_ping_start(PING_TYPE_TCP_SYN, "2001::", 1, 0, 100, NULL, NULL); if (check_ping) { fast_ping_stop(check_ping); is_ipv6_ready = 1; return; } if (errno == EADDRNOTAVAIL) { is_ipv6_ready = 0; return; } } } void _dns_server_set_dualstack_selection(struct dns_request *request) { struct dns_rule_flags *rule_flag = NULL; if (request->dualstack_selection_query || is_ipv6_ready == 0) { request->dualstack_selection = 0; return; } if ((request->prefetch_flags & PREFETCH_FLAGS_NO_DUALSTACK) != 0 || (request->prefetch_flags & PREFETCH_FLAGS_EXPIRED) != 0) { request->dualstack_selection = 0; return; } rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); if (rule_flag) { if (rule_flag->flags & DOMAIN_FLAG_DUALSTACK_SELECT) { request->dualstack_selection = 1; return; } if (rule_flag->is_flag_set & DOMAIN_FLAG_DUALSTACK_SELECT) { request->dualstack_selection = 0; return; } } if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_DUALSTACK_SELECTION) == 0) { request->dualstack_selection = 0; return; } request->dualstack_selection = request->conf->dualstack_ip_selection; } static void _dns_server_check_complete_dualstack(struct dns_request *request, struct dns_request *dualstack_request) { if (dualstack_request == NULL || request == NULL) { return; } if (dualstack_request->qtype == DNS_T_A && request->conf->dns_dualstack_ip_allow_force_AAAA == 0) { return; } if (dualstack_request->ping_time > 0) { return; } if (dualstack_request->dualstack_selection_query == 1) { return; } if (request->ping_time <= (request->conf->dns_dualstack_ip_selection_threshold * 10)) { return; } dualstack_request->dualstack_selection_has_ip = request->has_ip; dualstack_request->dualstack_selection_ping_time = request->ping_time; dualstack_request->dualstack_selection_force_soa = 1; _dns_server_request_complete(dualstack_request); } int _dns_server_force_dualstack(struct dns_request *request) { /* for dualstack request as first pending request, check if need to choose another request*/ if (request->dualstack_request) { struct dns_request *dualstack_request = request->dualstack_request; request->dualstack_selection_has_ip = dualstack_request->has_ip; request->dualstack_selection_ping_time = dualstack_request->ping_time; request->dualstack_selection = 1; /* if another request still waiting for ping, force complete another request */ _dns_server_check_complete_dualstack(request, dualstack_request); } if (request->dualstack_selection_ping_time < 0 || request->dualstack_selection == 0) { return -1; } if (request->has_soa || request->rcode != DNS_RC_NOERROR) { return -1; } if (request->dualstack_selection_has_ip == 0) { return -1; } if (request->ping_time > 0) { if (request->dualstack_selection_ping_time + (request->conf->dns_dualstack_ip_selection_threshold * 10) > request->ping_time) { return -1; } } if (request->qtype == DNS_T_A && request->conf->dns_dualstack_ip_allow_force_AAAA == 0) { return -1; } /* if ipv4 is fasting than ipv6, add ipv4 to cache, and return SOA for AAAA request */ tlog(TLOG_INFO, "result: %s, qtype: %d, force %s preferred, id: %d, time1: %d, time2: %d", request->domain, request->qtype, request->qtype == DNS_T_AAAA ? "IPv4" : "IPv6", request->id, request->ping_time, request->dualstack_selection_ping_time); request->dualstack_selection_force_soa = 1; return 0; } static int dns_server_dualstack_callback(const struct dns_result *result, void *user_ptr) { struct dns_request *request = (struct dns_request *)user_ptr; tlog(TLOG_DEBUG, "dualstack result: domain: %s, ip: %s, type: %d, ping: %d, rcode: %d", result->domain, result->ip, result->addr_type, result->ping_time, result->rtcode); if (request == NULL) { return -1; } if (result->rtcode == DNS_RC_NOERROR && result->ip[0] != 0) { request->dualstack_selection_has_ip = 1; } request->dualstack_selection_ping_time = result->ping_time; _dns_server_query_end(request); return 0; } int _dns_server_query_dualstack(struct dns_request *request) { int ret = -1; struct dns_request *request_dualstack = NULL; dns_type_t qtype = request->qtype; if (request->dualstack_selection == 0) { return 0; } if (qtype == DNS_T_A) { qtype = DNS_T_AAAA; } else if (qtype == DNS_T_AAAA) { qtype = DNS_T_A; } else { return 0; } request_dualstack = _dns_server_new_request(); if (request_dualstack == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); goto errout; } request_dualstack->server_flags = request->server_flags; safe_strncpy(request_dualstack->dns_group_name, request->dns_group_name, sizeof(request->dns_group_name)); safe_strncpy(request_dualstack->domain, request->domain, sizeof(request->domain)); request_dualstack->qtype = qtype; request_dualstack->dualstack_selection_query = 1; request_dualstack->has_cname_loop = request->has_cname_loop; request_dualstack->prefetch = request->prefetch; request_dualstack->prefetch_flags = request->prefetch_flags; request_dualstack->conf = request->conf; _dns_server_request_get(request); request_dualstack->dualstack_request = request; _dns_server_request_set_callback(request_dualstack, dns_server_dualstack_callback, request); request->request_wait++; ret = _dns_server_do_query(request_dualstack, 0); if (ret != 0) { request->request_wait--; tlog(TLOG_DEBUG, "do query %s type %d failed.\n", request->domain, qtype); goto errout; } _dns_server_request_release(request_dualstack); return ret; errout: if (request_dualstack) { _dns_server_request_set_callback(request_dualstack, NULL, NULL); _dns_server_request_release(request_dualstack); } _dns_server_request_release(request); return ret; } ================================================ FILE: src/dns_server/dualstack.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_DUALSTACK_ #define _DNS_SERVER_DUALSTACK_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ extern int is_ipv6_ready; int _dns_server_force_dualstack(struct dns_request *request); void _dns_server_set_dualstack_selection(struct dns_request *request); int _dns_server_query_dualstack(struct dns_request *request); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/ip_rule.c ================================================ /************************************************************************* * * 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 . */ #include "ip_rule.h" #include "dns_server.h" #include "neighbor.h" #include "rules.h" #include "soa.h" struct dns_client_rules *_dns_server_get_client_rules(struct sockaddr_storage *addr, socklen_t addr_len) { prefix_t prefix; radix_node_t *node = NULL; uint8_t netaddr[DNS_RR_AAAA_LEN] = {0}; struct dns_client_rules *client_rules = NULL; int netaddr_len = sizeof(netaddr); if (get_raw_addr_by_sockaddr(addr, addr_len, netaddr, &netaddr_len) != 0) { return NULL; } client_rules = _dns_server_get_client_rules_by_mac(netaddr, netaddr_len); if (client_rules != NULL) { return client_rules; } if (prefix_from_blob(netaddr, netaddr_len, netaddr_len * 8, &prefix) == NULL) { return NULL; } node = radix_search_best(dns_conf.client_rule.rule, &prefix); if (node == NULL) { return NULL; } client_rules = node->data; return client_rules; } static struct dns_ip_rules *_dns_server_ip_rule_get(struct dns_request *request, unsigned char *addr, int addr_len, dns_type_t addr_type) { prefix_t prefix; radix_node_t *node = NULL; struct dns_ip_rules *rule = NULL; if (request->conf == NULL) { return NULL; } /* Match IP address rules */ if (prefix_from_blob(addr, addr_len, addr_len * 8, &prefix) == NULL) { return NULL; } switch (prefix.family) { case AF_INET: node = radix_search_best(request->conf->address_rule.ipv4, &prefix); break; case AF_INET6: node = radix_search_best(request->conf->address_rule.ipv6, &prefix); break; default: break; } if (node == NULL) { return NULL; } if (node->data == NULL) { return NULL; } rule = node->data; return rule; } static int _dns_server_ip_rule_check(struct dns_request *request, struct dns_ip_rules *ip_rules, int result_flag) { struct ip_rule_flags *rule_flags = NULL; if (ip_rules == NULL) { goto rule_not_found; } struct dns_ip_rule *rule = ip_rules->rules[IP_RULE_FLAGS]; if (rule != NULL) { rule_flags = container_of(rule, struct ip_rule_flags, head); if (rule_flags != NULL) { if (rule_flags->flags & IP_RULE_FLAG_BOGUS) { request->rcode = DNS_RC_NXDOMAIN; request->has_soa = 1; request->force_soa = 1; _dns_server_setup_soa(request); goto nxdomain; } /* blacklist-ip */ if (rule_flags->flags & IP_RULE_FLAG_BLACKLIST) { if (result_flag & DNSSERVER_FLAG_BLACKLIST_IP) { goto match; } } /* ignore-ip */ if (rule_flags->flags & IP_RULE_FLAG_IP_IGNORE) { uint32_t domain_flags = _dns_server_get_rule_flags(request); if (domain_flags & DOMAIN_FLAG_NO_IGNORE_IP) { goto rule_not_found; } goto skip; } } } if (ip_rules->rules[IP_RULE_ALIAS] != NULL) { goto match; } rule_not_found: if (result_flag & DNSSERVER_FLAG_WHITELIST_IP) { if (rule_flags == NULL) { goto skip; } if (!(rule_flags->flags & IP_RULE_FLAG_WHITELIST)) { goto skip; } } return -1; skip: return -2; nxdomain: return -3; match: if (request->rcode == DNS_RC_SERVFAIL) { request->rcode = DNS_RC_NXDOMAIN; } return 0; } int _dns_server_process_ip_alias(struct dns_request *request, struct dns_iplist_ip_addresses *alias, unsigned char **paddrs, int *paddr_num, int max_paddr_num, int addr_len) { int addr_num = 0; if (alias == NULL) { return 0; } if (request == NULL) { return -1; } if (alias->ipaddr_num <= 0) { return 0; } for (int i = 0; i < alias->ipaddr_num && i < max_paddr_num; i++) { if (alias->ipaddr[i].addr_len != addr_len) { continue; } paddrs[i] = alias->ipaddr[i].addr; addr_num++; } *paddr_num = addr_num; return 0; } int _dns_server_process_ip_rule(struct dns_request *request, unsigned char *addr, int addr_len, dns_type_t addr_type, int result_flag, struct dns_iplist_ip_addresses **alias) { struct dns_ip_rules *ip_rules = NULL; int ret = 0; ip_rules = _dns_server_ip_rule_get(request, addr, addr_len, addr_type); ret = _dns_server_ip_rule_check(request, ip_rules, result_flag); if (ret != 0) { return ret; } if (ip_rules->rules[IP_RULE_ALIAS] && alias != NULL) { if (request->no_ipalias == 0) { struct ip_rule_alias *rule = container_of(ip_rules->rules[IP_RULE_ALIAS], struct ip_rule_alias, head); *alias = &rule->ip_alias; if (alias == NULL) { return 0; } } /* need process ip alias */ return -1; } return 0; } ================================================ FILE: src/dns_server/ip_rule.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_IP_RULE_ #define _DNS_SERVER_IP_RULE_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_process_ip_rule(struct dns_request *request, unsigned char *addr, int addr_len, dns_type_t addr_type, int result_flag, struct dns_iplist_ip_addresses **alias); int _dns_server_process_ip_alias(struct dns_request *request, struct dns_iplist_ip_addresses *alias, unsigned char **paddrs, int *paddr_num, int max_paddr_num, int addr_len); struct dns_client_rules *_dns_server_get_client_rules(struct sockaddr_storage *addr, socklen_t addr_len); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/ipset_nftset.c ================================================ /************************************************************************* * * 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 . */ #include "ipset_nftset.h" #include "dns_server.h" #include "smartdns/lib/nftset.h" #include "smartdns/util.h" void _dns_server_add_ipset_nftset(struct dns_request *request, struct dns_ipset_rule *ipset_rule, struct dns_nftset_rule *nftset_rule, const unsigned char addr[], int addr_len, int ipset_timeout_value, int nftset_timeout_value) { if (ipset_rule != NULL) { /* add IPV4 to ipset */ if (addr_len == DNS_RR_A_LEN) { tlog(TLOG_DEBUG, "IPSET-MATCH: domain: %s, ipset: %s, IP: %d.%d.%d.%d", request->domain, ipset_rule->ipsetname, addr[0], addr[1], addr[2], addr[3]); ipset_add(ipset_rule->ipsetname, addr, DNS_RR_A_LEN, ipset_timeout_value); } else if (addr_len == DNS_RR_AAAA_LEN) { tlog(TLOG_DEBUG, "IPSET-MATCH: domain: %s, ipset: %s, IP: " "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", request->domain, ipset_rule->ipsetname, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]); ipset_add(ipset_rule->ipsetname, addr, DNS_RR_AAAA_LEN, ipset_timeout_value); } } if (nftset_rule != NULL) { /* add IPV4 to ipset */ if (addr_len == DNS_RR_A_LEN) { tlog(TLOG_DEBUG, "NFTSET-MATCH: domain: %s, nftset: %s %s %s, IP: %d.%d.%d.%d", request->domain, nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr[0], addr[1], addr[2], addr[3]); nftset_add(nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr, DNS_RR_A_LEN, nftset_timeout_value); } else if (addr_len == DNS_RR_AAAA_LEN) { tlog(TLOG_DEBUG, "NFTSET-MATCH: domain: %s, nftset: %s %s %s, IP: " "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", request->domain, nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]); nftset_add(nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr, DNS_RR_AAAA_LEN, nftset_timeout_value); } } } void *_dns_server_get_bind_ipset_nftset_rule(struct dns_request *request, enum domain_rule type) { if (request->conn == NULL) { return NULL; } if (request->conn->ipset_nftset_rule == NULL) { return NULL; } switch (type) { case DOMAIN_RULE_IPSET: return request->conn->ipset_nftset_rule->ipset; case DOMAIN_RULE_IPSET_IPV4: return request->conn->ipset_nftset_rule->ipset_ip; case DOMAIN_RULE_IPSET_IPV6: return request->conn->ipset_nftset_rule->ipset_ip6; case DOMAIN_RULE_NFTSET_IP: return request->conn->ipset_nftset_rule->nftset_ip; case DOMAIN_RULE_NFTSET_IP6: return request->conn->ipset_nftset_rule->nftset_ip6; default: break; } return NULL; } ================================================ FILE: src/dns_server/ipset_nftset.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_IPSET_NFTSET_ #define _DNS_SERVER_IPSET_NFTSET_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_server_add_ipset_nftset(struct dns_request *request, struct dns_ipset_rule *ipset_rule, struct dns_nftset_rule *nftset_rule, const unsigned char addr[], int addr_len, int ipset_timeout_value, int nftset_timeout_value); void *_dns_server_get_bind_ipset_nftset_rule(struct dns_request *request, enum domain_rule type); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/local_addr.c ================================================ /************************************************************************* * * 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 . */ #include "local_addr.h" #include "dns_server.h" #include #include #include #include #include #include static void _dns_server_local_addr_cache_add(unsigned char *netaddr, int netaddr_len, int prefix_len) { prefix_t prefix; struct local_addr_cache_item *addr_cache_item = NULL; radix_node_t *node = NULL; if (prefix_from_blob(netaddr, netaddr_len, prefix_len, &prefix) == NULL) { return; } node = radix_lookup(server.local_addr_cache.addr, &prefix); if (node == NULL) { goto errout; } if (node->data == NULL) { addr_cache_item = zalloc(1, sizeof(struct local_addr_cache_item)); if (addr_cache_item == NULL) { return; } } else { addr_cache_item = node->data; } addr_cache_item->ip_addr_len = netaddr_len; memcpy(addr_cache_item->ip_addr, netaddr, netaddr_len); addr_cache_item->mask_len = prefix_len; node->data = addr_cache_item; return; errout: if (addr_cache_item) { free(addr_cache_item); } return; } static void _dns_server_local_addr_cache_del(unsigned char *netaddr, int netaddr_len, int prefix_len) { radix_node_t *node = NULL; prefix_t prefix; if (prefix_from_blob(netaddr, netaddr_len, prefix_len, &prefix) == NULL) { return; } node = radix_search_exact(server.local_addr_cache.addr, &prefix); if (node == NULL) { return; } if (node->data != NULL) { free(node->data); } node->data = NULL; radix_remove(server.local_addr_cache.addr, node); } void _dns_server_process_local_addr_cache(int fd_netlink, struct epoll_event *event, unsigned long now) { char buffer[1024 * 8]; struct iovec iov = {buffer, sizeof(buffer)}; struct sockaddr_nl sa; struct msghdr msg; struct nlmsghdr *nh; memset(&msg, 0, sizeof(msg)); msg.msg_name = &sa; msg.msg_namelen = sizeof(sa); msg.msg_iov = &iov; msg.msg_iovlen = 1; while (1) { ssize_t len = recvmsg(fd_netlink, &msg, 0); if (len == -1) { break; } for (nh = (struct nlmsghdr *)buffer; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) { if (nh->nlmsg_type == NLMSG_DONE) { break; } if (nh->nlmsg_type == NLMSG_ERROR) { break; } if (nh->nlmsg_type != RTM_NEWADDR && nh->nlmsg_type != RTM_DELADDR) { continue; } struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nh); struct rtattr *rth = IFA_RTA(ifa); int rtl = IFA_PAYLOAD(nh); while (rtl && RTA_OK(rth, rtl)) { if (rth->rta_type == IFA_ADDRESS) { unsigned char *netaddr = RTA_DATA(rth); int netaddr_len = 0; if (ifa->ifa_family == AF_INET) { netaddr_len = 4; } else if (ifa->ifa_family == AF_INET6) { netaddr_len = 16; } else { continue; } if (nh->nlmsg_type == RTM_NEWADDR) { _dns_server_local_addr_cache_add(netaddr, netaddr_len, netaddr_len * 8); _dns_server_local_addr_cache_add(netaddr, netaddr_len, ifa->ifa_prefixlen); } else { _dns_server_local_addr_cache_del(netaddr, netaddr_len, netaddr_len * 8); _dns_server_local_addr_cache_del(netaddr, netaddr_len, ifa->ifa_prefixlen); } } rth = RTA_NEXT(rth, rtl); } } } } static void _dns_server_local_addr_cache_item_free(radix_node_t *node, void *cbctx) { struct local_addr_cache_item *cache_item = NULL; if (node == NULL) { return; } if (node->data == NULL) { return; } cache_item = node->data; free(cache_item); node->data = NULL; } int _dns_server_local_addr_cache_destroy(void) { if (server.local_addr_cache.addr) { Destroy_Radix(server.local_addr_cache.addr, _dns_server_local_addr_cache_item_free, NULL); server.local_addr_cache.addr = NULL; } if (server.local_addr_cache.fd_netlink > 0) { close(server.local_addr_cache.fd_netlink); server.local_addr_cache.fd_netlink = -1; } return 0; } int _dns_server_local_addr_cache_init(void) { int fd = -1; struct sockaddr_nl sa; server.local_addr_cache.fd_netlink = -1; server.local_addr_cache.addr = NULL; if (dns_conf.local_ptr_enable == 0) { return 0; } fd = socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, NETLINK_ROUTE); if (fd < 0) { tlog(TLOG_WARN, "create netlink socket failed, %s", strerror(errno)); goto errout; } memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_groups = RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_IFADDR; if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { tlog(TLOG_WARN, "bind netlink socket failed, %s", strerror(errno)); goto errout; } struct epoll_event event; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLERR; event.data.fd = fd; if (epoll_ctl(server.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { tlog(TLOG_ERROR, "set eventfd failed, %s\n", strerror(errno)); goto errout; } server.local_addr_cache.fd_netlink = fd; server.local_addr_cache.addr = New_Radix(); struct { struct nlmsghdr nh; struct rtgenmsg gen; } request; memset(&request, 0, sizeof(request)); request.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); request.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; request.nh.nlmsg_type = RTM_GETADDR; request.gen.rtgen_family = AF_UNSPEC; if (send(fd, &request, request.nh.nlmsg_len, 0) < 0) { tlog(TLOG_WARN, "send netlink request failed, %s", strerror(errno)); goto errout; } return 0; errout: if (fd > 0) { close(fd); } return -1; } ================================================ FILE: src/dns_server/local_addr.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_LOCAL_ADDR_ #define _DNS_SERVER_LOCAL_ADDR_ #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_local_addr_cache_destroy(void); int _dns_server_local_addr_cache_init(void); void _dns_server_process_local_addr_cache(int fd_netlink, struct epoll_event *event, unsigned long now); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/mdns.c ================================================ /************************************************************************* * * 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 . */ #include "mdns.h" #include "dns_server.h" #include "request.h" void _dns_server_need_append_mdns_local_cname(struct dns_request *request) { if (request->is_mdns_lookup == 0) { return; } if (request->has_cname != 0) { return; } if (request->domain[0] == '\0') { return; } if (strstr(request->domain, ".") != NULL) { return; } request->has_cname = 1; snprintf(request->cname, sizeof(request->cname), "%.*s.%s", (int)(sizeof(request->cname) - sizeof(DNS_SERVER_GROUP_LOCAL) - 1), request->domain, DNS_SERVER_GROUP_LOCAL); return; } void _dns_server_mdns_query_setup_server_group(struct dns_request *request, const char **group_name) { if (request->is_mdns_lookup == 0 || group_name == NULL) { return; } *group_name = DNS_SERVER_GROUP_MDNS; safe_strncpy(request->dns_group_name, *group_name, sizeof(request->dns_group_name)); return; } int _dns_server_mdns_query_setup(struct dns_request *request, const char *server_group_name, char **request_domain, char *domain_buffer, int domain_buffer_len) { if (dns_conf.mdns_lookup != 1) { return 0; } switch (request->qtype) { case DNS_T_A: case DNS_T_AAAA: case DNS_T_SRV: if (request->domain[0] != '\0' && strstr(request->domain, ".") == NULL) { snprintf(domain_buffer, domain_buffer_len, "%s.%s", request->domain, DNS_SERVER_GROUP_LOCAL); *request_domain = domain_buffer; _dns_server_set_request_mdns(request); } if (server_group_name != NULL && strncmp(server_group_name, DNS_SERVER_GROUP_MDNS, DNS_GROUP_NAME_LEN) == 0) { _dns_server_set_request_mdns(request); } break; default: break; } return 0; } ================================================ FILE: src/dns_server/mdns.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_MDNS_ #define _DNS_SERVER_MDNS_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_server_need_append_mdns_local_cname(struct dns_request *request); void _dns_server_mdns_query_setup_server_group(struct dns_request *request, const char **group_name); int _dns_server_mdns_query_setup(struct dns_request *request, const char *server_group_name, char **request_domain, char *domain_buffer, int domain_buffer_len); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/neighbor.c ================================================ /************************************************************************* * * 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 . */ #include "neighbor.h" #include "dns_server.h" #include "smartdns/fast_ping.h" #include #include #include #include void dns_server_enable_update_neighbor_cache(int enable) { if (enable) { server.update_neighbor_cache = 1; } else { if (dns_conf.client_rule.mac_num > 0) { return; } server.update_neighbor_cache = 0; } } static void _dns_server_neighbor_cache_free_item(struct neighbor_cache_item *item) { hash_del(&item->node); list_del_init(&item->list); free(item); atomic_dec(&server.neighbor_cache.cache_num); } void _dns_server_neighbor_cache_remove_all(void) { struct neighbor_cache_item *item = NULL; struct hlist_node *tmp = NULL; unsigned long bucket = 0; hash_for_each_safe(server.neighbor_cache.cache, bucket, tmp, item, node) { _dns_server_neighbor_cache_free_item(item); } pthread_mutex_destroy(&server.neighbor_cache.lock); } int _dns_server_neighbor_cache_init(void) { hash_init(server.neighbor_cache.cache); INIT_LIST_HEAD(&server.neighbor_cache.list); atomic_set(&server.neighbor_cache.cache_num, 0); pthread_mutex_init(&server.neighbor_cache.lock, NULL); if (dns_conf.client_rule.mac_num > 0) { server.update_neighbor_cache = 1; } return 0; } static void _dns_server_neighbor_cache_free_last_used_item(void) { struct neighbor_cache_item *item = NULL; if (atomic_read(&server.neighbor_cache.cache_num) < DNS_SERVER_NEIGHBOR_CACHE_MAX_NUM) { return; } item = list_last_entry(&server.neighbor_cache.list, struct neighbor_cache_item, list); if (item == NULL) { return; } _dns_server_neighbor_cache_free_item(item); } struct neighbor_cache_item *_dns_server_neighbor_cache_get_item(const uint8_t *net_addr, int net_addr_len) { struct neighbor_cache_item *item = NULL, *item_result = NULL; uint32_t key = 0; key = jhash(net_addr, net_addr_len, 0); hash_for_each_possible(server.neighbor_cache.cache, item, node, key) { if (item->ip_addr_len != net_addr_len) { continue; } if (memcmp(item->ip_addr, net_addr, net_addr_len) != 0) { continue; } item_result = item; break; } return item_result; } static int _dns_server_neighbor_cache_add(const uint8_t *net_addr, int net_addr_len, const uint8_t *mac) { struct neighbor_cache_item *item = NULL; uint32_t key = 0; if (net_addr_len > DNS_RR_AAAA_LEN) { return -1; } item = _dns_server_neighbor_cache_get_item(net_addr, net_addr_len); if (item == NULL) { item = zalloc(1, sizeof(*item)); if (item == NULL) { return -1; } INIT_LIST_HEAD(&item->list); INIT_HLIST_NODE(&item->node); } memcpy(item->ip_addr, net_addr, net_addr_len); item->ip_addr_len = net_addr_len; item->last_update_time = time(NULL); if (mac == NULL) { item->has_mac = 0; } else { memcpy(item->mac, mac, 6); item->has_mac = 1; } key = jhash(net_addr, net_addr_len, 0); hash_del(&item->node); hash_add(server.neighbor_cache.cache, &item->node, key); list_del_init(&item->list); list_add(&item->list, &server.neighbor_cache.list); atomic_inc(&server.neighbor_cache.cache_num); _dns_server_neighbor_cache_free_last_used_item(); return 0; } static int _dns_server_neighbors_callback(const uint8_t *net_addr, int net_addr_len, const uint8_t mac[6], void *arg) { struct neighbor_enum_args *args = arg; _dns_server_neighbor_cache_add(net_addr, net_addr_len, mac); if (net_addr_len != args->netaddr_len) { return 0; } if (memcmp(net_addr, args->netaddr, net_addr_len) != 0) { return 0; } args->group_mac = dns_server_rule_group_mac_get(mac); return 1; } static int _dns_server_neighbor_cache_is_valid(struct neighbor_cache_item *item) { if (item == NULL) { return -1; } time_t now = time(NULL); if (item->last_update_time + DNS_SERVER_NEIGHBOR_CACHE_TIMEOUT < now) { return -1; } if (item->has_mac) { return 0; } if (item->last_update_time + DNS_SERVER_NEIGHBOR_CACHE_NOMAC_TIMEOUT < now) { return -1; } return 0; } struct dns_client_rules *_dns_server_get_client_rules_by_mac(uint8_t *netaddr, int netaddr_len) { struct client_roue_group_mac *group_mac = NULL; struct neighbor_cache_item *item = NULL; int family = AF_UNSPEC; int ret = 0; struct neighbor_enum_args args; if (server.update_neighbor_cache == 0) { return NULL; } item = _dns_server_neighbor_cache_get_item(netaddr, netaddr_len); if (_dns_server_neighbor_cache_is_valid(item) == 0) { if (item->has_mac == 0) { return NULL; } group_mac = dns_server_rule_group_mac_get(item->mac); if (group_mac != NULL) { return group_mac->rules; } return NULL; } if (netaddr_len == 4) { family = AF_INET; } else if (netaddr_len == 16) { family = AF_INET6; } args.group_mac = group_mac; args.netaddr = netaddr; args.netaddr_len = netaddr_len; for (int i = 0; i < 1; i++) { ret = netlink_get_neighbors(family, netaddr, netaddr_len, _dns_server_neighbors_callback, &args); if (ret < 0) { goto add_cache; } } if (args.group_mac == NULL) { return NULL; } return args.group_mac->rules; add_cache: _dns_server_neighbor_cache_add(netaddr, netaddr_len, NULL); int probe_fd = socket(family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); if (probe_fd >= 0) { struct sockaddr_storage dest; memset(&dest, 0, sizeof(dest)); dest.ss_family = family; if (family == AF_INET) { struct sockaddr_in *in = (struct sockaddr_in *)&dest; memcpy(&in->sin_addr, netaddr, 4); in->sin_port = htons(53); /* dummy port */ connect(probe_fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_in)); } else if (family == AF_INET6) { struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&dest; memcpy(&in6->sin6_addr, netaddr, 16); in6->sin6_port = htons(53); /* dummy port */ connect(probe_fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_in6)); } close(probe_fd); } return NULL; } ================================================ FILE: src/dns_server/neighbor.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_NEIGHBOR_ #define _DNS_SERVER_NEIGHBOR_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ struct neighbor_cache_item *_dns_server_neighbor_cache_get_item(const uint8_t *net_addr, int net_addr_len); struct dns_client_rules *_dns_server_get_client_rules_by_mac(uint8_t *netaddr, int netaddr_len); int _dns_server_neighbor_cache_init(void); void _dns_server_neighbor_cache_remove_all(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/prefetch.c ================================================ /************************************************************************* * * 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 . */ #include "prefetch.h" #include "cache.h" #include "dns_server.h" #include "request.h" #include "smartdns/dns_cache.h" int _dns_server_prefetch_request(char *domain, dns_type_t qtype, struct dns_server_query_option *server_query_option, int prefetch_flag) { int ret = -1; struct dns_request *request = NULL; request = _dns_server_new_request(); if (request == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); goto errout; } request->prefetch = 1; request->prefetch_flags = prefetch_flag; safe_strncpy(request->domain, domain, sizeof(request->domain)); request->qtype = qtype; _dns_server_setup_server_query_options(request, server_query_option); ret = _dns_server_do_query(request, 0); if (ret != 0) { tlog(TLOG_DEBUG, "prefetch do query %s failed.\n", request->domain); goto errout; } _dns_server_request_release(request); return ret; errout: if (request) { _dns_server_request_release(request); } return ret; } dns_cache_tmout_action_t _dns_server_prefetch_domain(struct dns_conf_group *conf_group, struct dns_cache *dns_cache) { /* If there are still hits, continue pre-fetching */ struct dns_server_query_option server_query_option; int hitnum = dns_cache_hitnum_dec_get(dns_cache); if (hitnum <= 0) { return DNS_CACHE_TMOUT_ACTION_DEL; } /* start prefetch domain */ tlog(TLOG_DEBUG, "prefetch by cache %s, qtype %d, ttl %d, hitnum %d", dns_cache->info.domain, dns_cache->info.qtype, dns_cache->info.ttl, hitnum); server_query_option.dns_group_name = dns_cache_get_dns_group_name(dns_cache); server_query_option.server_flags = dns_cache_get_query_flag(dns_cache); server_query_option.ecs_enable_flag = 0; if (_dns_server_prefetch_request(dns_cache->info.domain, dns_cache->info.qtype, &server_query_option, PREFETCH_FLAGS_NO_DUALSTACK) != 0) { tlog(TLOG_ERROR, "prefetch domain %s, qtype %d, failed.", dns_cache->info.domain, dns_cache->info.qtype); return DNS_CACHE_TMOUT_ACTION_RETRY; } return DNS_CACHE_TMOUT_ACTION_OK; } dns_cache_tmout_action_t _dns_server_prefetch_expired_domain(struct dns_conf_group *conf_group, struct dns_cache *dns_cache) { time_t ttl = _dns_server_expired_cache_ttl(dns_cache, conf_group->dns_serve_expired_ttl); if (ttl <= 1) { return DNS_CACHE_TMOUT_ACTION_DEL; } /* start prefetch domain */ tlog(TLOG_DEBUG, "expired domain, total %d, prefetch by cache %s, qtype %d, ttl %llu, rcode %d, insert time %llu replace time " "%llu", dns_cache_total_num(), dns_cache->info.domain, dns_cache->info.qtype, (unsigned long long)ttl, dns_cache->info.rcode, (unsigned long long)dns_cache->info.insert_time, (unsigned long long)dns_cache->info.replace_time); struct dns_server_query_option server_query_option; server_query_option.dns_group_name = dns_cache_get_dns_group_name(dns_cache); server_query_option.server_flags = dns_cache_get_query_flag(dns_cache); server_query_option.ecs_enable_flag = 0; if (_dns_server_prefetch_request(dns_cache->info.domain, dns_cache->info.qtype, &server_query_option, PREFETCH_FLAGS_EXPIRED) != 0) { tlog(TLOG_DEBUG, "prefetch domain %s, qtype %d, failed.", dns_cache->info.domain, dns_cache->info.qtype); return DNS_CACHE_TMOUT_ACTION_RETRY; } return DNS_CACHE_TMOUT_ACTION_OK; } ================================================ FILE: src/dns_server/prefetch.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_PREFETCH_ #define _DNS_SERVER_PREFETCH_ #include "dns_server.h" #include "smartdns/dns_cache.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_prefetch_request(char *domain, dns_type_t qtype, struct dns_server_query_option *server_query_option, int prefetch_flag); dns_cache_tmout_action_t _dns_server_prefetch_domain(struct dns_conf_group *conf_group, struct dns_cache *dns_cache); dns_cache_tmout_action_t _dns_server_prefetch_expired_domain(struct dns_conf_group *conf_group, struct dns_cache *dns_cache); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/ptr.c ================================================ /************************************************************************* * * 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 . */ #include "ptr.h" #include "context.h" #include "dns_server.h" #include "mdns.h" #include "request.h" #include "rules.h" #include "soa.h" #include static int _dns_server_is_private_address(const unsigned char *addr, int addr_len) { if (addr_len == 4) { if (addr[0] == 10 || (addr[0] == 172 && addr[1] >= 16 && addr[1] <= 31) || (addr[0] == 192 && addr[1] == 168)) { return 0; } } else if (addr_len == 16) { if (addr[0] == 0xfe && addr[1] == 0x80) { return 0; } } return -1; } int _dns_server_get_inet_by_addr(struct sockaddr_storage *localaddr, struct sockaddr_storage *addr, int family) { struct ifaddrs *ifaddr = NULL; struct ifaddrs *ifa = NULL; char ethname[16] = {0}; if (getifaddrs(&ifaddr) == -1) { return -1; } for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) { continue; } if (localaddr->ss_family != ifa->ifa_addr->sa_family) { continue; } switch (ifa->ifa_addr->sa_family) { case AF_INET: { struct sockaddr_in *addr_in_1 = NULL; struct sockaddr_in *addr_in_2 = NULL; addr_in_1 = (struct sockaddr_in *)ifa->ifa_addr; addr_in_2 = (struct sockaddr_in *)localaddr; if (memcmp(&(addr_in_1->sin_addr.s_addr), &(addr_in_2->sin_addr.s_addr), 4) != 0) { continue; } } break; case AF_INET6: { struct sockaddr_in6 *addr_in6_1 = NULL; struct sockaddr_in6 *addr_in6_2 = NULL; addr_in6_1 = (struct sockaddr_in6 *)ifa->ifa_addr; addr_in6_2 = (struct sockaddr_in6 *)localaddr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6_1->sin6_addr)) { unsigned char *addr1 = addr_in6_1->sin6_addr.s6_addr + 12; unsigned char *addr2 = addr_in6_2->sin6_addr.s6_addr + 12; if (memcmp(addr1, addr2, 4) != 0) { continue; } } else { unsigned char *addr1 = addr_in6_1->sin6_addr.s6_addr; unsigned char *addr2 = addr_in6_2->sin6_addr.s6_addr; if (memcmp(addr1, addr2, 16) != 0) { continue; } } } break; default: continue; break; } safe_strncpy(ethname, ifa->ifa_name, sizeof(ethname)); break; } if (ethname[0] == '\0') { goto errout; } for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) { continue; } if (ifa->ifa_addr->sa_family != family) { continue; } if (strncmp(ethname, ifa->ifa_name, sizeof(ethname)) != 0) { continue; } if (family == AF_INET) { memcpy(addr, ifa->ifa_addr, sizeof(struct sockaddr_in)); } else if (family == AF_INET6) { memcpy(addr, ifa->ifa_addr, sizeof(struct sockaddr_in6)); } break; } if (ifa == NULL) { goto errout; } freeifaddrs(ifaddr); return 0; errout: if (ifaddr) { freeifaddrs(ifaddr); } return -1; } static int _dns_server_parser_addr_from_apra(const char *arpa, unsigned char *addr, int *addr_len, int max_addr_len) { int high, low; char *endptr = NULL; if (arpa == NULL || addr == NULL || addr_len == NULL || max_addr_len < 4) { return -1; } int ret = sscanf(arpa, "%hhu.%hhu.%hhu.%hhu.in-addr.arpa", &addr[3], &addr[2], &addr[1], &addr[0]); if (ret == 4 && strstr(arpa, ".in-addr.arpa") != NULL) { *addr_len = 4; return 0; } if (max_addr_len != 16) { return -1; } for (int i = 15; i >= 0; i--) { low = strtol(arpa, &endptr, 16); if (endptr == NULL || *endptr != '.' || *endptr == '\0') { return -1; } arpa = endptr + 1; high = strtol(arpa, &endptr, 16); if (endptr == NULL || *endptr != '.' || *endptr == '\0') { return -1; } arpa = endptr + 1; addr[i] = (high << 4) | low; } if (strstr(arpa, "ip6.arpa") == NULL) { return -1; } *addr_len = 16; return 0; } int _dns_server_process_ptr_query(struct dns_request *request) { if (request->qtype != DNS_T_PTR) { return -1; } if (_dns_server_process_ptr(request) == 0) { return 0; } request->passthrough = 1; return -1; } int _dns_server_process_ptrs(struct dns_request *request) { uint32_t key = 0; struct dns_ptr *ptr = NULL; struct dns_ptr *ptr_tmp = NULL; key = hash_string(request->domain); hash_for_each_possible(dns_ptr_table.ptr, ptr_tmp, node, key) { if (strncmp(ptr_tmp->ptr_domain, request->domain, DNS_MAX_PTR_LEN) != 0) { continue; } ptr = ptr_tmp; break; } if (ptr == NULL) { goto errout; } request->has_ptr = 1; safe_strncpy(request->ptr_hostname, ptr->hostname, DNS_MAX_CNAME_LEN); return 0; errout: return -1; } int _dns_server_process_ptr(struct dns_request *request) { if (_dns_server_process_ptrs(request) == 0) { goto reply_exit; } if (_dns_server_process_local_ptr(request) == 0) { goto reply_exit; } return -1; reply_exit: request->rcode = DNS_RC_NOERROR; request->ip_ttl = _dns_server_get_local_ttl(request); struct dns_server_post_context context; _dns_server_post_context_init(&context, request); context.do_reply = 1; context.do_audit = 0; context.do_cache = 1; _dns_request_post(&context); return 0; } int _dns_server_process_local_ptr(struct dns_request *request) { unsigned char ptr_addr[16]; int ptr_addr_len = 0; int found = 0; prefix_t prefix; radix_node_t *node = NULL; struct local_addr_cache_item *addr_cache_item = NULL; struct dns_nameserver_rule *ptr_nameserver_rule; if (_dns_server_parser_addr_from_apra(request->domain, ptr_addr, &ptr_addr_len, sizeof(ptr_addr)) != 0) { /* Determine if the smartdns service is in effect. */ if (strncasecmp(request->domain, "smartdns", sizeof("smartdns")) != 0) { return -1; } found = 1; goto out; } if (dns_conf.local_ptr_enable == 0) { goto out; } if (prefix_from_blob(ptr_addr, ptr_addr_len, ptr_addr_len * 8, &prefix) == NULL) { goto out; } node = radix_search_best(server.local_addr_cache.addr, &prefix); if (node == NULL) { goto out; } if (node->data == NULL) { goto out; } addr_cache_item = node->data; if (addr_cache_item->mask_len == ptr_addr_len * 8) { found = 1; goto out; } if (dns_conf.mdns_lookup) { _dns_server_set_request_mdns(request); goto errout; } out: ptr_nameserver_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_NAMESERVER); if (ptr_nameserver_rule != NULL && ptr_nameserver_rule->group_name[0] != 0) { goto errout; } if (found == 0 && _dns_server_is_private_address(ptr_addr, ptr_addr_len) == 0) { request->has_soa = 1; _dns_server_setup_soa(request); goto clear; } if (found == 0) { goto errout; } char full_hostname[DNS_MAX_CNAME_LEN]; if (dns_server_get_server_name(full_hostname, sizeof(full_hostname)) != 0) { goto errout; } request->has_ptr = 1; safe_strncpy(request->ptr_hostname, full_hostname, DNS_MAX_CNAME_LEN); clear: return 0; errout: return -1; } int _dns_server_get_local_ttl(struct dns_request *request) { struct dns_ttl_rule *ttl_rule; /* get domain rule flag */ ttl_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_TTL); if (ttl_rule != NULL) { if (ttl_rule->ttl > 0) { return ttl_rule->ttl; } } if (dns_conf.local_ttl > 0) { return dns_conf.local_ttl; } if (request->conf->dns_rr_ttl > 0) { return request->conf->dns_rr_ttl; } if (request->conf->dns_rr_ttl_min > 0) { return request->conf->dns_rr_ttl_min; } return DNS_SERVER_ADDR_TTL; } ================================================ FILE: src/dns_server/ptr.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_PTR_ #define _DNS_SERVER_PTR_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_get_local_ttl(struct dns_request *request); int _dns_server_process_local_ptr(struct dns_request *request); int _dns_server_process_ptrs(struct dns_request *request); int _dns_server_process_ptr(struct dns_request *request); int _dns_server_get_inet_by_addr(struct sockaddr_storage *localaddr, struct sockaddr_storage *addr, int family); int _dns_server_process_ptr_query(struct dns_request *request); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/request.c ================================================ /************************************************************************* * * 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 . */ #include "request.h" #include "address.h" #include "connection.h" #include "context.h" #include "ddr.h" #include "dns64.h" #include "dns_server.h" #include "dualstack.h" #include "mdns.h" #include "neighbor.h" #include "ptr.h" #include "request_pending.h" #include "rules.h" #include "soa.h" #include "smartdns/dns_plugin.h" #include "smartdns/dns_stats.h" int _dns_server_has_bind_flag(struct dns_request *request, uint32_t flag) { if (request->server_flags & flag) { return 0; } return -1; } static int _dns_server_request_complete_with_all_IPs(struct dns_request *request, int with_all_ips) { int ttl = 0; struct dns_server_post_context context; if (request->rcode == DNS_RC_SERVFAIL) { ttl = DNS_SERVER_FAIL_TTL; } if (request->ip_ttl == 0) { request->ip_ttl = ttl; } if (request->prefetch == 1) { return 0; } if (atomic_inc_return(&request->notified) != 1) { return 0; } if (request->has_ip != 0 && request->passthrough == 0) { request->has_soa = 0; if (request->has_ping_result == 0 && request->ip_ttl > DNS_SERVER_TMOUT_TTL) { request->ip_ttl = DNS_SERVER_TMOUT_TTL; } ttl = request->ip_ttl; } if (_dns_server_force_dualstack(request) == 0) { goto out; } _dns_server_need_append_mdns_local_cname(request); if (request->has_soa) { tlog(TLOG_INFO, "result: %s, qtype: %d, SOA", request->domain, request->qtype); } else { if (request->qtype == DNS_T_A) { tlog(TLOG_INFO, "result: %s, qtype: %d, rtt: %.1f ms, %d.%d.%d.%d", request->domain, request->qtype, ((float)request->ping_time) / 10, request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3]); } else if (request->qtype == DNS_T_AAAA) { tlog(TLOG_INFO, "result: %s, qtype: %d, rtt: %.1f ms, " "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", request->domain, request->qtype, ((float)request->ping_time) / 10, request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3], request->ip_addr[4], request->ip_addr[5], request->ip_addr[6], request->ip_addr[7], request->ip_addr[8], request->ip_addr[9], request->ip_addr[10], request->ip_addr[11], request->ip_addr[12], request->ip_addr[13], request->ip_addr[14], request->ip_addr[15]); } if (request->rcode == DNS_RC_SERVFAIL && request->has_ip) { request->rcode = DNS_RC_NOERROR; } } out: _dns_server_post_context_init(&context, request); context.do_cache = 1; context.do_ipset = 1; context.do_force_soa = request->dualstack_selection_force_soa | request->force_soa; context.do_audit = 1; context.do_reply = 1; context.reply_ttl = _dns_server_get_reply_ttl(request, ttl); context.skip_notify_count = 1; context.select_all_best_ip = with_all_ips; context.no_release_parent = 1; _dns_request_post(&context); return _dns_server_reply_all_pending_list(request, &context); } int _dns_server_request_complete(struct dns_request *request) { return _dns_server_request_complete_with_all_IPs(request, 0); } void _dns_server_request_remove_all(void) { struct dns_request *request = NULL; struct dns_request *tmp = NULL; LIST_HEAD(remove_list); pthread_mutex_lock(&server.request_list_lock); list_for_each_entry_safe(request, tmp, &server.request_list, list) { list_add_tail(&request->check_list, &remove_list); _dns_server_request_get(request); } pthread_mutex_unlock(&server.request_list_lock); list_for_each_entry_safe(request, tmp, &remove_list, check_list) { _dns_server_request_complete(request); _dns_server_request_release(request); } } static void _dns_server_delete_request(struct dns_request *request) { if (atomic_read(&request->notified) == 0) { _dns_server_request_complete(request); } if (request->conn) { _dns_server_conn_release(request->conn); } pthread_mutex_destroy(&request->ip_map_lock); struct dns_request_https *https_svcb, *tmp_https; list_for_each_entry_safe(https_svcb, tmp_https, &request->https_svcb_list, list) { list_del(&https_svcb->list); free(https_svcb); } struct dns_request_srv *srv, *tmp_srv; list_for_each_entry_safe(srv, tmp_srv, &request->srv_list, list) { list_del(&srv->list); free(srv); } if (request->original_domain) { free(request->original_domain); } memset(request, 0, sizeof(*request)); free(request); atomic_dec(&server.request_num); } static void _dns_server_complete_with_multi_ipaddress(struct dns_request *request) { struct dns_server_post_context context; int do_reply = 0; if (atomic_read(&request->ip_map_num) > 0) { request->has_soa = 0; } if (atomic_inc_return(&request->notified) == 1) { do_reply = 1; _dns_server_force_dualstack(request); } if (request->passthrough && do_reply == 0) { return; } _dns_server_need_append_mdns_local_cname(request); _dns_server_post_context_init(&context, request); context.do_cache = 1; context.do_ipset = 1; context.do_reply = do_reply; context.do_log_result = 1; context.select_all_best_ip = 1; context.skip_notify_count = 1; context.do_force_soa = request->dualstack_selection_force_soa | request->force_soa; _dns_request_post(&context); _dns_server_reply_all_pending_list(request, &context); } void _dns_server_request_release_complete(struct dns_request *request, int do_complete) { struct dns_ip_address *addr_map = NULL; struct hlist_node *tmp = NULL; unsigned long bucket = 0; pthread_mutex_lock(&server.request_list_lock); int refcnt = atomic_dec_return(&request->refcnt); if (refcnt) { pthread_mutex_unlock(&server.request_list_lock); if (refcnt < 0) { BUG("BUG: refcnt is %d, domain %s, qtype %d", refcnt, request->domain, request->qtype); } return; } list_del_init(&request->list); list_del_init(&request->check_list); pthread_mutex_unlock(&server.request_list_lock); pthread_mutex_lock(&server.request_pending_lock); list_del_init(&request->pending_list); pthread_mutex_unlock(&server.request_pending_lock); if (do_complete && atomic_read(&request->plugin_complete_called) == 0) { /* Select max hit ip address, and return to client */ _dns_server_select_possible_ipaddress(request); _dns_server_complete_with_multi_ipaddress(request); } if (request->parent_request != NULL) { _dns_server_request_release(request->parent_request); request->parent_request = NULL; } atomic_inc(&request->refcnt); if (atomic_inc_return(&request->plugin_complete_called) == 1) { smartdns_plugin_func_server_complete_request(request); } if (atomic_dec_return(&request->refcnt) > 0) { /* plugin may hold request. */ return; } pthread_mutex_lock(&request->ip_map_lock); hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node) { hash_del(&addr_map->node); free(addr_map); } pthread_mutex_unlock(&request->ip_map_lock); if (request->rcode == DNS_RC_NOERROR) { stats_inc(&dns_stats.request.success_count); } if (request->conn) { dns_stats_avg_time_add(request->query_time); } _dns_server_delete_request(request); } void _dns_server_request_release(struct dns_request *request) { _dns_server_request_release_complete(request, 1); } void _dns_server_request_get(struct dns_request *request) { if (atomic_inc_return(&request->refcnt) <= 0) { BUG("BUG: request ref is invalid, %s", request->domain); } } const struct sockaddr *dns_server_request_get_remote_addr(struct dns_request *request) { if (request->conn == NULL) { return NULL; } return &request->addr; } const struct sockaddr *dns_server_request_get_local_addr(struct dns_request *request) { if (request == NULL) { return NULL; } return (struct sockaddr *)&request->localaddr; } const uint8_t *dns_server_request_get_remote_mac(struct dns_request *request) { if (request->conn == NULL) { return NULL; } return request->mac; }; const char *dns_server_request_get_group_name(struct dns_request *request) { if (request == NULL) { return NULL; } return request->dns_group_name; } const char *dns_server_request_get_domain(struct dns_request *request) { if (request == NULL) { return NULL; } return request->domain; } int dns_server_request_get_qtype(struct dns_request *request) { if (request == NULL) { return 0; } return request->qtype; } int dns_server_request_get_qclass(struct dns_request *request) { if (request == NULL) { return 0; } return request->qclass; } int dns_server_request_get_query_time(struct dns_request *request) { if (request == NULL) { return -1; } return request->query_time; } uint64_t dns_server_request_get_query_timestamp(struct dns_request *request) { if (request == NULL) { return 0; } return request->query_timestamp; } float dns_server_request_get_ping_time(struct dns_request *request) { if (request == NULL) { return 0; } return (float)request->ping_time / 10; } int dns_server_request_is_prefetch(struct dns_request *request) { if (request == NULL) { return 0; } return request->prefetch; } int dns_server_request_is_dualstack(struct dns_request *request) { if (request == NULL) { return 0; } return request->dualstack_selection_query; } int dns_server_request_is_blocked(struct dns_request *request) { if (request == NULL) { return 0; } if (request->qtype == DNS_T_HTTPS || request->qtype == DNS_T_SVCB) { uint32_t flags = _dns_server_get_rule_flags(request); if (flags & DOMAIN_FLAG_ADDR_HTTPS_SOA) { return 1; } return 0; } return _dns_server_is_return_soa(request); } int dns_server_request_is_cached(struct dns_request *request) { if (request == NULL) { return 0; } return request->is_cache_reply; } int dns_server_request_get_id(struct dns_request *request) { if (request == NULL) { return 0; } return request->id; } int dns_server_request_get_rcode(struct dns_request *request) { if (request == NULL) { return DNS_RC_SERVFAIL; } return request->rcode; } void dns_server_request_get(struct dns_request *request) { _dns_server_request_get(request); } void dns_server_request_put(struct dns_request *request) { _dns_server_request_release(request); } void dns_server_request_set_private(struct dns_request *request, void *private_data) { if (request == NULL) { return; } request->private_data = private_data; } void *dns_server_request_get_private(struct dns_request *request) { if (request == NULL) { return NULL; } return request->private_data; } struct dns_request *_dns_server_new_request(void) { struct dns_request *request = NULL; request = zalloc(1, sizeof(*request)); if (request == NULL) { tlog(TLOG_ERROR, "malloc request failed.\n"); goto errout; } pthread_mutex_init(&request->ip_map_lock, NULL); atomic_set(&request->adblock, 0); atomic_set(&request->soa_num, 0); atomic_set(&request->ip_map_num, 0); atomic_set(&request->refcnt, 0); atomic_set(&request->notified, 0); atomic_set(&request->do_callback, 0); atomic_set(&request->plugin_complete_called, 0); request->ping_time = -1; request->prefetch = 0; request->dualstack_selection = 0; request->dualstack_selection_ping_time = -1; request->rcode = DNS_RC_SERVFAIL; request->conn = NULL; request->qclass = DNS_C_IN; request->result_callback = NULL; request->conf = dns_server_get_default_rule_group(); request->check_order_list = &dns_conf.default_check_orders; request->response_mode = dns_conf.default_response_mode; request->query_timestamp = get_utc_time_ms(); INIT_LIST_HEAD(&request->list); INIT_LIST_HEAD(&request->pending_list); INIT_LIST_HEAD(&request->check_list); INIT_LIST_HEAD(&request->https_svcb_list); INIT_LIST_HEAD(&request->srv_list); hash_init(request->ip_map); _dns_server_request_get(request); atomic_add(1, &server.request_num); stats_inc(&dns_stats.request.total); return request; errout: return NULL; } void _dns_server_query_end(struct dns_request *request) { int ip_num = 0; int request_wait = 0; struct dns_conf_group *conf = request->conf; /* if mdns request timeout */ if (request->is_mdns_lookup == 1 && request->rcode == DNS_RC_SERVFAIL) { request->rcode = DNS_RC_NOERROR; request->force_soa = 1; request->ip_ttl = _dns_server_get_conf_ttl(request, DNS_SERVER_ADDR_TTL); } pthread_mutex_lock(&request->ip_map_lock); ip_num = atomic_read(&request->ip_map_num); request_wait = request->request_wait; request->request_wait--; pthread_mutex_unlock(&request->ip_map_lock); /* Not need to wait check result if only has one ip address */ if (ip_num <= 1 && request_wait == 1) { if (request->dualstack_selection_query == 1) { if ((conf->ipset_nftset.ipset_no_speed.ipv4_enable || conf->ipset_nftset.nftset_no_speed.ip_enable || conf->ipset_nftset.ipset_no_speed.ipv6_enable || conf->ipset_nftset.nftset_no_speed.ip6_enable) && request->conf->dns_dns64.prefix_len == 0) { /* if speed check fail enabled, we need reply quickly, otherwise wait for ping result.*/ _dns_server_request_complete(request); } goto out; } if (request->dualstack_selection_has_ip && request->dualstack_selection_ping_time > 0) { goto out; } request->has_ping_result = 1; _dns_server_request_complete(request); } out: _dns_server_request_release(request); } void _dns_server_passthrough_may_complete(struct dns_request *request) { const unsigned char *addr; if (request->passthrough != 2) { return; } if (request->has_ip == 0 && request->has_soa == 0) { return; } if (request->qtype == DNS_T_A && request->has_ip == 1) { /* Ad blocking result */ addr = request->ip_addr; if (addr[0] == 0 || addr[0] == 127) { /* If half of the servers return the same result, then ignore this address */ if (atomic_read(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) { return; } } } if (request->qtype == DNS_T_AAAA && request->has_ip == 1) { addr = request->ip_addr; if (_dns_server_is_adblock_ipv6(addr) == 0) { /* If half of the servers return the same result, then ignore this address */ if (atomic_read(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) { return; } } } _dns_server_request_complete_with_all_IPs(request, 1); } static int _dns_server_reply_request_eth_ip(struct dns_request *request) { struct sockaddr_in *addr_in = NULL; struct sockaddr_in6 *addr_in6 = NULL; struct sockaddr_storage *localaddr = NULL; struct sockaddr_storage localaddr_buff; localaddr = &request->localaddr; /* address /domain/ rule */ switch (request->qtype) { case DNS_T_A: if (localaddr->ss_family != AF_INET) { if (_dns_server_get_inet_by_addr(localaddr, &localaddr_buff, AF_INET) != 0) { _dns_server_reply_SOA(DNS_RC_NOERROR, request); return 0; } localaddr = &localaddr_buff; } addr_in = (struct sockaddr_in *)localaddr; memcpy(request->ip_addr, &addr_in->sin_addr.s_addr, DNS_RR_A_LEN); break; case DNS_T_AAAA: if (localaddr->ss_family != AF_INET6) { if (_dns_server_get_inet_by_addr(localaddr, &localaddr_buff, AF_INET6) != 0) { _dns_server_reply_SOA(DNS_RC_NOERROR, request); return 0; } localaddr = &localaddr_buff; } addr_in6 = (struct sockaddr_in6 *)localaddr; memcpy(request->ip_addr, &addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN); break; default: goto out; break; } request->rcode = DNS_RC_NOERROR; request->ip_ttl = dns_conf.local_ttl; request->has_ip = 1; struct dns_server_post_context context; _dns_server_post_context_init(&context, request); context.do_reply = 1; _dns_request_post(&context); return 0; out: return -1; } void _dns_server_set_request_mdns(struct dns_request *request) { if (dns_conf.mdns_lookup != 1) { return; } request->is_mdns_lookup = 1; } static int _dns_server_process_local_SOA(struct dns_request *request) { struct dns_soa *soa = NULL; char *mname = "ns.local"; char *rname = "admin.local"; if (strncasecmp("local", request->domain, DNS_MAX_CNAME_LEN) != 0) { mname = "ns.lan"; rname = "admin.lan"; if (strncasecmp("lan", request->domain, DNS_MAX_CNAME_LEN) != 0) { return -1; } } soa = &request->soa; safe_strncpy(soa->mname, mname, DNS_MAX_CNAME_LEN); safe_strncpy(soa->rname, rname, DNS_MAX_CNAME_LEN); soa->serial = 1; soa->refresh = 3600; soa->retry = 900; soa->expire = 604800; soa->minimum = 86400; return _dns_server_reply_SOA_ext(DNS_RC_NOERROR, request); } int _dns_server_process_srv(struct dns_request *request) { struct dns_srv_record *srv_record; struct dns_request_srv *srv; struct dns_srv_record_rule *srv_rule = (struct dns_srv_record_rule *)_dns_server_get_dns_rule(request, DOMAIN_RULE_SRV); if (srv_rule == NULL) { return -1; } request->rcode = DNS_RC_NOERROR; request->ip_ttl = _dns_server_get_local_ttl(request); list_for_each_entry(srv_record, &srv_rule->record_list, list) { srv = zalloc(1, sizeof(*srv)); if (srv == NULL) { continue; } safe_strncpy(srv->host, srv_record->host, sizeof(srv->host)); srv->priority = srv_record->priority; srv->weight = srv_record->weight; srv->port = srv_record->port; list_add_tail(&srv->list, &request->srv_list); } struct dns_server_post_context context; _dns_server_post_context_init(&context, request); context.do_audit = 1; context.do_reply = 1; context.do_cache = 0; context.do_force_soa = 0; _dns_request_post(&context); return 0; } int _dns_server_process_svcb(struct dns_request *request) { if (strncasecmp("_dns.resolver.arpa", request->domain, DNS_MAX_CNAME_LEN) == 0) { return _dns_server_process_DDR(request); } return -1; } int _dns_server_pre_process_server_flags(struct dns_request *request) { if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_CACHE) == 0) { request->no_cache = 1; } if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_IP_ALIAS) == 0) { request->no_ipalias = 1; } if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_PREFETCH) == 0) { request->prefetch_flags |= PREFETCH_FLAGS_NOPREFETCH; } if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_SERVE_EXPIRED) == 0) { request->no_serve_expired = 1; } if (request->qtype == DNS_T_HTTPS && _dns_server_has_bind_flag(request, BIND_FLAG_FORCE_HTTPS_SOA) == 0) { _dns_server_reply_SOA(DNS_RC_NOERROR, request); return 0; } return -1; } struct dns_request *_dns_server_new_child_request(struct dns_request *request, const char *domain, dns_type_t qtype, child_request_callback child_callback) { struct dns_request *child_request = NULL; child_request = _dns_server_new_request(); if (child_request == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); goto errout; } child_request->server_flags = request->server_flags; safe_strncpy(child_request->dns_group_name, request->dns_group_name, sizeof(request->dns_group_name)); safe_strncpy(child_request->domain, domain, sizeof(child_request->domain)); child_request->prefetch = request->prefetch; child_request->prefetch_flags = request->prefetch_flags; child_request->child_callback = child_callback; child_request->parent_request = request; child_request->qtype = qtype; child_request->qclass = request->qclass; child_request->conf = request->conf; if (request->has_ecs) { memcpy(&child_request->ecs, &request->ecs, sizeof(child_request->ecs)); child_request->has_ecs = request->has_ecs; } _dns_server_request_get(request); /* reference count is 1 hold by parent request */ request->child_request = child_request; _dns_server_get_domain_rule(child_request); return child_request; errout: if (child_request) { _dns_server_request_release(child_request); } return NULL; } int _dns_server_request_copy(struct dns_request *request, struct dns_request *from) { unsigned long bucket = 0; struct dns_ip_address *addr_map = NULL; struct hlist_node *tmp = NULL; uint32_t key = 0; int addr_len = 0; request->rcode = from->rcode; if (from->has_ip) { request->has_ip = 1; request->ip_ttl = _dns_server_get_conf_ttl(request, from->ip_ttl); request->ping_time = from->ping_time; memcpy(request->ip_addr, from->ip_addr, sizeof(request->ip_addr)); } if (from->has_cname) { request->has_cname = 1; request->ttl_cname = from->ttl_cname; safe_strncpy(request->cname, from->cname, sizeof(request->cname)); } if (from->has_soa) { request->has_soa = 1; memcpy(&request->soa, &from->soa, sizeof(request->soa)); } pthread_mutex_lock(&request->ip_map_lock); hash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node) { hash_del(&addr_map->node); free(addr_map); } pthread_mutex_unlock(&request->ip_map_lock); pthread_mutex_lock(&from->ip_map_lock); hash_for_each_safe(from->ip_map, bucket, tmp, addr_map, node) { struct dns_ip_address *new_addr_map = NULL; if (addr_map->addr_type == DNS_T_A) { addr_len = DNS_RR_A_LEN; } else if (addr_map->addr_type == DNS_T_AAAA) { addr_len = DNS_RR_AAAA_LEN; } else { continue; } new_addr_map = malloc(sizeof(struct dns_ip_address)); if (new_addr_map == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); pthread_mutex_unlock(&from->ip_map_lock); return -1; } memcpy(new_addr_map, addr_map, sizeof(struct dns_ip_address)); new_addr_map->ping_time = addr_map->ping_time; key = jhash(new_addr_map->ip_addr, addr_len, 0); key = jhash(&addr_map->addr_type, sizeof(addr_map->addr_type), key); pthread_mutex_lock(&request->ip_map_lock); hash_add(request->ip_map, &new_addr_map->node, key); pthread_mutex_unlock(&request->ip_map_lock); } pthread_mutex_unlock(&from->ip_map_lock); return 0; } const char *_dns_server_get_request_server_groupname(struct dns_request *request) { if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_NAMESERVER) == 0) { return NULL; } /* Get the nameserver rule */ if (request->domain_rule.rules[DOMAIN_RULE_NAMESERVER]) { struct dns_nameserver_rule *nameserver_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_NAMESERVER); return nameserver_rule->group_name; } return NULL; } int _dns_server_get_expired_ttl_reply(struct dns_request *request, struct dns_cache *dns_cache) { int ttl = dns_cache_get_ttl(dns_cache); if (ttl > 0) { return ttl; } return request->conf->dns_serve_expired_reply_ttl; } int _dns_server_process_https_svcb(struct dns_request *request) { struct dns_https_record_rule *https_record_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_HTTPS); struct dns_https_record *record; struct dns_request_https *svcb; if (request->qtype != DNS_T_HTTPS) { return 0; } if (!list_empty(&request->https_svcb_list)) { return 0; } if (https_record_rule == NULL) { return 0; } list_for_each_entry(record, &https_record_rule->record_list, list) { if (record->enable == 0) { continue; } svcb = zalloc(1, sizeof(*svcb)); if (svcb == NULL) { continue; } safe_strncpy(svcb->domain, request->domain, sizeof(svcb->domain)); safe_strncpy(svcb->target, record->target, sizeof(svcb->target)); svcb->priority = record->priority; svcb->port = record->port; memcpy(svcb->ech, record->ech, record->ech_len); svcb->ech_len = record->ech_len; memcpy(svcb->alpn, record->alpn, sizeof(svcb->alpn)); svcb->alpn_len = record->alpn_len; if (record->has_ipv4) { svcb->has_ipv4 = 1; memcpy(svcb->ipv4_addr, record->ipv4_addr, sizeof(svcb->ipv4_addr)); request->has_ip = 1; } if (record->has_ipv6) { svcb->has_ipv6 = 1; memcpy(svcb->ipv6_addr, record->ipv6_addr, sizeof(svcb->ipv6_addr)); request->has_ip = 1; } list_add_tail(&svcb->list, &request->https_svcb_list); } if (!list_empty(&request->https_svcb_list)) { request->rcode = DNS_RC_NOERROR; return -1; } return 0; } void _dns_server_request_set_client(struct dns_request *request, struct dns_server_conn_head *conn) { request->conn = conn; request->server_flags = conn->server_flags; _dns_server_conn_get(conn); } void _dns_server_request_set_id(struct dns_request *request, unsigned short id) { request->id = id; } void _dns_server_request_set_mac(struct dns_request *request, struct sockaddr_storage *from, socklen_t from_len) { uint8_t netaddr[DNS_RR_AAAA_LEN] = {0}; int netaddr_len = sizeof(netaddr); if (get_raw_addr_by_sockaddr(from, from_len, netaddr, &netaddr_len) != 0) { return; } struct neighbor_cache_item *item = _dns_server_neighbor_cache_get_item(netaddr, netaddr_len); if (item) { if (item->has_mac) { memcpy(request->mac, item->mac, 6); } } } int _dns_server_request_set_client_addr(struct dns_request *request, struct sockaddr_storage *from, socklen_t from_len) { switch (from->ss_family) { case AF_INET: memcpy(&request->in, from, from_len); request->addr_len = from_len; break; case AF_INET6: memcpy(&request->in6, from, from_len); request->addr_len = from_len; break; default: return -1; break; } return 0; } void _dns_server_request_set_callback(struct dns_request *request, dns_result_callback callback, void *user_ptr) { request->result_callback = callback; request->user_ptr = user_ptr; } int _dns_server_process_smartdns_domain(struct dns_request *request) { uint32_t flags = _dns_server_get_rule_flags(request); if (_dns_server_is_dns_rule_extract_match(request, DOMAIN_RULE_FLAGS) == 0) { return -1; } if (!(flags & DOMAIN_FLAG_SMARTDNS_DOMAIN)) { return -1; } return _dns_server_reply_request_eth_ip(request); } int _dns_server_process_special_query(struct dns_request *request) { int ret = 0; switch (request->qtype) { case DNS_T_PTR: break; case DNS_T_SOA: ret = _dns_server_process_local_SOA(request); if (ret == 0) { goto clean_exit; } else { /* pass to upstream server */ request->passthrough = 1; } break; case DNS_T_SRV: ret = _dns_server_process_srv(request); if (ret == 0) { goto clean_exit; } else { /* pass to upstream server */ request->passthrough = 1; } case DNS_T_HTTPS: break; case DNS_T_SVCB: ret = _dns_server_process_svcb(request); if (ret == 0) { goto clean_exit; } else { /* pass to upstream server */ request->passthrough = 1; } break; case DNS_T_A: break; case DNS_T_AAAA: break; default: tlog(TLOG_DEBUG, "unsupported qtype: %d, domain: %s", request->qtype, request->domain); request->passthrough = 1; /* pass request to upstream server */ break; } return -1; clean_exit: return 0; } void _dns_server_check_set_passthrough(struct dns_request *request) { if (request->check_order_list->orders[0].type == DOMAIN_CHECK_NONE) { request->passthrough = 1; } if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_SPEED_CHECK) == 0) { request->passthrough = 1; } if (is_ipv6_ready == 0 && request->qtype == DNS_T_AAAA) { request->passthrough = 1; } if (request->passthrough == 1) { request->dualstack_selection = 0; } if (request->passthrough == 1 && (request->qtype == DNS_T_A || request->qtype == DNS_T_AAAA || request->qtype == DNS_T_HTTPS) && request->edns0_do == 0) { request->passthrough = 2; } } int _dns_server_process_host(struct dns_request *request) { uint32_t key = 0; struct dns_hosts *host = NULL; struct dns_hosts *host_tmp = NULL; int dns_type = request->qtype; if (dns_hosts_record_num <= 0) { return -1; } key = hash_string_case(request->domain); key = jhash(&dns_type, sizeof(dns_type), key); hash_for_each_possible(dns_hosts_table.hosts, host_tmp, node, key) { if (host_tmp->dns_type != dns_type) { continue; } if (strncasecmp(host_tmp->domain, request->domain, DNS_MAX_CNAME_LEN) != 0) { continue; } host = host_tmp; break; } if (host == NULL) { return -1; } if (host->is_soa) { request->has_soa = 1; return _dns_server_reply_SOA(DNS_RC_NOERROR, request); } switch (request->qtype) { case DNS_T_A: memcpy(request->ip_addr, host->ipv4_addr, DNS_RR_A_LEN); break; case DNS_T_AAAA: memcpy(request->ip_addr, host->ipv6_addr, DNS_RR_AAAA_LEN); break; default: goto errout; break; } request->rcode = DNS_RC_NOERROR; request->ip_ttl = dns_conf.local_ttl; request->has_ip = 1; struct dns_server_post_context context; _dns_server_post_context_init(&context, request); context.do_reply = 1; context.do_audit = 1; _dns_request_post(&context); return 0; errout: return -1; } int _dns_server_setup_query_option(struct dns_request *request, struct dns_query_options *options) { options->enable_flag = 0; if (request->has_ecs) { memcpy(&options->ecs_dns, &request->ecs, sizeof(options->ecs_dns)); options->enable_flag |= DNS_QUEY_OPTION_ECS_DNS; } if (request->edns0_do) { options->enable_flag |= DNS_QUEY_OPTION_EDNS0_DO; } options->conf_group_name = request->dns_group_name; return 0; } int _dns_server_setup_request_conf_pre(struct dns_request *request) { struct dns_conf_group *rule_group = NULL; struct dns_request_domain_rule domain_rule; if (request->skip_domain_rule != 0 && request->conf) { return 0; } if (request->conn && request->conn->dns_group != NULL && request->dns_group_name[0] == '\0') { safe_strncpy(request->dns_group_name, request->conn->dns_group, sizeof(request->dns_group_name)); } rule_group = dns_server_get_rule_group(request->dns_group_name); if (rule_group == NULL) { return -1; } request->conf = rule_group; memset(&domain_rule, 0, sizeof(domain_rule)); _dns_server_get_domain_rule_by_domain_ext(rule_group, &domain_rule, DOMAIN_RULE_GROUP, request->domain, 1); if (domain_rule.rules[DOMAIN_RULE_GROUP] == NULL) { return 0; } struct dns_group_rule *group_rule = _dns_server_get_dns_rule_ext(&domain_rule, DOMAIN_RULE_GROUP); if (group_rule == NULL) { return 0; } rule_group = dns_server_get_rule_group(group_rule->group_name); if (rule_group == NULL) { return 0; } request->conf = rule_group; safe_strncpy(request->dns_group_name, rule_group->group_name, sizeof(request->dns_group_name)); tlog(TLOG_DEBUG, "domain %s match group %s", request->domain, rule_group->group_name); return 0; } int _dns_server_setup_request_conf(struct dns_request *request) { struct dns_conf_group *rule_group = NULL; rule_group = dns_server_get_rule_group(request->dns_group_name); if (rule_group == NULL) { return -1; } request->conf = rule_group; request->check_order_list = &rule_group->check_orders; return 0; } void _dns_server_setup_dns_group_name(struct dns_request *request, const char **server_group_name) { const char *group_name = NULL; const char *temp_group_name = NULL; temp_group_name = _dns_server_get_request_server_groupname(request); if (temp_group_name != NULL) { group_name = temp_group_name; } if (request->dns_group_name[0] != '\0' && group_name == NULL) { group_name = request->dns_group_name; } else { safe_strncpy(request->dns_group_name, group_name, sizeof(request->dns_group_name)); } *server_group_name = group_name; } int _dns_server_check_request_supported(struct dns_request *request, struct dns_packet *packet) { if (request->qclass != DNS_C_IN) { return -1; } if (packet->head.opcode != DNS_OP_QUERY) { return -1; } return 0; } int _dns_server_parser_request(struct dns_request *request, struct dns_packet *packet) { struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int qclass = 0; int qtype = DNS_T_ALL; char domain[DNS_MAX_CNAME_LEN]; if (packet->head.qr != DNS_QR_QUERY) { goto errout; } /* get request domain and request qtype */ rrs = dns_get_rrs_start(packet, DNS_RRS_QD, &rr_count); if (rr_count > 1 || rr_count <= 0) { goto errout; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { ret = dns_get_domain(rrs, domain, sizeof(domain), &qtype, &qclass); if (ret != 0) { goto errout; } // Only support one question. int case_changed = 0; safe_strncpy_lower(request->domain, domain, sizeof(request->domain), &case_changed); /* support draft dns0x20 */ if (case_changed) { request->original_domain = malloc(DNS_MAX_CNAME_LEN); if (request->original_domain == NULL) { tlog(TLOG_ERROR, "malloc failed.\n"); goto errout; } safe_strncpy(request->original_domain, domain, DNS_MAX_CNAME_LEN); tlog(TLOG_DEBUG, "query %s by origin domain %s", request->domain, request->original_domain); } request->qtype = qtype; break; } request->qclass = qclass; if (_dns_server_check_request_supported(request, packet) != 0) { goto errout; } if ((dns_get_OPT_option(packet) & DNS_OPT_FLAG_DO) && packet->head.ad == 1) { request->edns0_do = 1; } /* get request opts */ rr_count = 0; rrs = dns_get_rrs_start(packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return 0; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { switch (rrs->type) { case DNS_OPT_T_TCP_KEEPALIVE: { unsigned short idle_timeout = 0; ret = dns_get_OPT_TCP_KEEPALIVE(rrs, &idle_timeout); if (idle_timeout == 0 || ret != 0) { continue; } tlog(TLOG_DEBUG, "set tcp connection timeout to %u", idle_timeout); _dns_server_update_request_connection_timeout(request->conn, idle_timeout / 10); } break; case DNS_OPT_T_ECS: ret = dns_get_OPT_ECS(rrs, &request->ecs); if (ret != 0) { continue; } request->has_ecs = 1; default: break; } } return 0; errout: request->rcode = DNS_RC_NOTIMP; return -1; } int _dns_server_setup_server_query_options(struct dns_request *request, struct dns_server_query_option *server_query_option) { if (server_query_option == NULL) { return 0; } request->server_flags = server_query_option->server_flags; if (server_query_option->dns_group_name) { safe_strncpy(request->dns_group_name, server_query_option->dns_group_name, DNS_GROUP_NAME_LEN); } if (server_query_option->ecs_enable_flag & DNS_QUEY_OPTION_ECS_DNS) { request->has_ecs = 1; memcpy(&request->ecs, &server_query_option->ecs_dns, sizeof(request->ecs)); } if (server_query_option->ecs_enable_flag & DNS_QUEY_OPTION_EDNS0_DO) { request->edns0_do = 1; } return 0; } ================================================ FILE: src/dns_server/request.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_REQUEST_ #define _DNS_SERVER_REQUEST_ #include "dns_server.h" #include "smartdns/dns.h" #include "smartdns/dns_cache.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _dns_server_request_release_complete(struct dns_request *request, int do_complete); void _dns_server_query_end(struct dns_request *request); void *dns_server_request_get_private(struct dns_request *request); struct dns_request *_dns_server_new_request(void); struct dns_request *_dns_server_new_child_request(struct dns_request *request, const char *domain, dns_type_t qtype, child_request_callback child_callback); const char *_dns_server_get_request_server_groupname(struct dns_request *request); int _dns_server_request_complete(struct dns_request *request); int _dns_server_request_copy(struct dns_request *request, struct dns_request *from); void _dns_server_request_set_callback(struct dns_request *request, dns_result_callback callback, void *user_ptr); int _dns_server_setup_request_conf_pre(struct dns_request *request); int _dns_server_setup_request_conf(struct dns_request *request); int _dns_server_setup_server_query_options(struct dns_request *request, struct dns_server_query_option *server_query_option); int _dns_server_request_set_client_addr(struct dns_request *request, struct sockaddr_storage *from, socklen_t from_len); int _dns_server_check_request_supported(struct dns_request *request, struct dns_packet *packet); int _dns_server_parser_request(struct dns_request *request, struct dns_packet *packet); void _dns_server_set_request_mdns(struct dns_request *request); int _dns_server_process_svcb(struct dns_request *request); int _dns_server_process_srv(struct dns_request *request); int _dns_server_process_host(struct dns_request *request); void _dns_server_check_set_passthrough(struct dns_request *request); int _dns_server_process_special_query(struct dns_request *request); int _dns_server_process_dns64(struct dns_request *request); void _dns_server_setup_dns_group_name(struct dns_request *request, const char **server_group_name); int _dns_server_setup_query_option(struct dns_request *request, struct dns_query_options *options); int _dns_server_process_smartdns_domain(struct dns_request *request); void _dns_server_passthrough_may_complete(struct dns_request *request); int _dns_server_pre_process_server_flags(struct dns_request *request); int _dns_server_get_expired_ttl_reply(struct dns_request *request, struct dns_cache *dns_cache); int _dns_server_process_https_svcb(struct dns_request *request); void _dns_server_request_set_client(struct dns_request *request, struct dns_server_conn_head *conn); void _dns_server_request_set_id(struct dns_request *request, unsigned short id); void _dns_server_request_set_mac(struct dns_request *request, struct sockaddr_storage *from, socklen_t from_len); int _dns_server_has_bind_flag(struct dns_request *request, uint32_t flag); int _dns_server_is_dns64_request(struct dns_request *request); void _dns_server_request_release(struct dns_request *request); void _dns_server_request_get(struct dns_request *request); void _dns_server_request_remove_all(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/request_pending.c ================================================ /************************************************************************* * * 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 . */ #include "request_pending.h" #include "answer.h" #include "context.h" #include "dns_server.h" #include "request.h" int _dns_server_set_to_pending_list(struct dns_request *request) { struct dns_request_pending_list *pending_list = NULL; struct dns_request_pending_list *pending_list_tmp = NULL; uint32_t key = 0; int ret = -1; if (request->qtype != DNS_T_A && request->qtype != DNS_T_AAAA) { return ret; } key = hash_string(request->domain); key = hash_string_initval(request->dns_group_name, key); key = jhash(&(request->qtype), sizeof(request->qtype), key); key = jhash(&(request->server_flags), sizeof(request->server_flags), key); pthread_mutex_lock(&server.request_pending_lock); hash_for_each_possible(server.request_pending, pending_list_tmp, node, key) { if (request->qtype != pending_list_tmp->qtype) { continue; } if (request->server_flags != pending_list_tmp->server_flags) { continue; } if (strcmp(request->dns_group_name, pending_list_tmp->dns_group_name) != 0) { continue; } if (strncmp(request->domain, pending_list_tmp->domain, DNS_MAX_CNAME_LEN) != 0) { continue; } pending_list = pending_list_tmp; break; } if (pending_list == NULL) { pending_list = zalloc(1, sizeof(*pending_list)); if (pending_list == NULL) { ret = -1; goto out; } pthread_mutex_init(&pending_list->request_list_lock, NULL); INIT_LIST_HEAD(&pending_list->request_list); INIT_HLIST_NODE(&pending_list->node); pending_list->qtype = request->qtype; pending_list->server_flags = request->server_flags; safe_strncpy(pending_list->domain, request->domain, DNS_MAX_CNAME_LEN); safe_strncpy(pending_list->dns_group_name, request->dns_group_name, DNS_GROUP_NAME_LEN); hash_add(server.request_pending, &pending_list->node, key); request->request_pending_list = pending_list; } else { ret = 0; } if (ret == 0) { _dns_server_request_get(request); } list_add_tail(&request->pending_list, &pending_list->request_list); out: pthread_mutex_unlock(&server.request_pending_lock); return ret; } int _dns_server_reply_all_pending_list(struct dns_request *request, struct dns_server_post_context *context) { struct dns_request_pending_list *pending_list = NULL; struct dns_request *req = NULL; struct dns_request *tmp = NULL; int ret = 0; pthread_mutex_lock(&server.request_pending_lock); if (request->request_pending_list == NULL) { pthread_mutex_unlock(&server.request_pending_lock); return 0; } pending_list = request->request_pending_list; request->request_pending_list = NULL; hlist_del_init(&pending_list->node); pthread_mutex_unlock(&server.request_pending_lock); pthread_mutex_lock(&pending_list->request_list_lock); list_del_init(&request->pending_list); list_for_each_entry_safe(req, tmp, &(pending_list->request_list), pending_list) { struct dns_server_post_context context_pending; _dns_server_post_context_init_from(&context_pending, req, context->packet, context->inpacket, context->inpacket_len); req->dualstack_selection = request->dualstack_selection; req->dualstack_selection_query = request->dualstack_selection_query; req->dualstack_selection_force_soa = request->dualstack_selection_force_soa; req->dualstack_selection_has_ip = request->dualstack_selection_has_ip; req->dualstack_selection_ping_time = request->dualstack_selection_ping_time; req->ping_time = request->ping_time; req->is_cache_reply = request->is_cache_reply; _dns_server_get_answer(&context_pending); context_pending.is_cache_reply = context->is_cache_reply; context_pending.do_cache = 0; context_pending.do_audit = context->do_audit; context_pending.do_reply = context->do_reply; context_pending.do_force_soa = context->do_force_soa; context_pending.do_ipset = 0; context_pending.reply_ttl = request->ip_ttl; context_pending.no_release_parent = 0; _dns_server_reply_passthrough(&context_pending); req->request_pending_list = NULL; list_del_init(&req->pending_list); _dns_server_request_release_complete(req, 0); } pthread_mutex_unlock(&pending_list->request_list_lock); free(pending_list); return ret; } ================================================ FILE: src/dns_server/request_pending.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_REQUEST_PENDING_ #define _DNS_SERVER_REQUEST_PENDING_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_reply_all_pending_list(struct dns_request *request, struct dns_server_post_context *context); int _dns_server_set_to_pending_list(struct dns_request *request); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/rules.c ================================================ /************************************************************************* * * 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 . */ #include "rules.h" #include "address.h" #include "dns_server.h" #include "ip_rule.h" #include "request.h" #include "request_pending.h" #include "soa.h" void *_dns_server_get_dns_rule_ext(struct dns_request_domain_rule *domain_rule, enum domain_rule rule) { if (rule >= DOMAIN_RULE_MAX || domain_rule == NULL) { return NULL; } return domain_rule->rules[rule]; } static int _dns_server_is_dns_rule_extract_match_ext(struct dns_request_domain_rule *domain_rule, enum domain_rule rule) { if (rule >= DOMAIN_RULE_MAX || domain_rule == NULL) { return 0; } return domain_rule->is_sub_rule[rule] == 0; } static void _dns_server_log_rule(const char *domain, enum domain_rule rule_type, unsigned char *rule_key, int rule_key_len) { char rule_name[DNS_MAX_CNAME_LEN] = {0}; if (rule_key_len <= 0) { return; } reverse_string(rule_name, (char *)rule_key, rule_key_len, 1); rule_name[rule_key_len] = 0; tlog(TLOG_INFO, "RULE-MATCH, type: %d, domain: %s, rule: %s", rule_type, domain, rule_name); } static void _dns_server_update_rule_by_flags(struct dns_request_domain_rule *request_domain_rule) { unsigned int flags = 0; flags = request_domain_rule->flags; if (flags & DOMAIN_FLAG_ADDR_IGN) { request_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV4] = NULL; request_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV6] = NULL; } if (flags & DOMAIN_FLAG_ADDR_IPV4_IGN) { request_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV4] = NULL; } if (flags & DOMAIN_FLAG_ADDR_IPV6_IGN) { request_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV6] = NULL; } if (flags & DOMAIN_FLAG_ADDR_HTTPS_IGN) { request_domain_rule->rules[DOMAIN_RULE_HTTPS] = NULL; } if (flags & DOMAIN_FLAG_IPSET_IGN) { request_domain_rule->rules[DOMAIN_RULE_IPSET] = NULL; } if (flags & DOMAIN_FLAG_IPSET_IPV4_IGN) { request_domain_rule->rules[DOMAIN_RULE_IPSET_IPV4] = NULL; } if (flags & DOMAIN_FLAG_IPSET_IPV6_IGN) { request_domain_rule->rules[DOMAIN_RULE_IPSET_IPV6] = NULL; } if (flags & DOMAIN_FLAG_NFTSET_IP_IGN || flags & DOMAIN_FLAG_NFTSET_INET_IGN) { request_domain_rule->rules[DOMAIN_RULE_NFTSET_IP] = NULL; } if (flags & DOMAIN_FLAG_NFTSET_IP6_IGN || flags & DOMAIN_FLAG_NFTSET_INET_IGN) { request_domain_rule->rules[DOMAIN_RULE_NFTSET_IP6] = NULL; } if (flags & DOMAIN_FLAG_NAMESERVER_IGNORE) { request_domain_rule->rules[DOMAIN_RULE_NAMESERVER] = NULL; } } static int _dns_server_get_rules(unsigned char *key, uint32_t key_len, int is_subkey, void *value, void *arg) { struct rule_walk_args *walk_args = arg; struct dns_request_domain_rule *request_domain_rule = walk_args->args; struct dns_domain_rule *domain_rule = value; int i = 0; if (domain_rule == NULL) { return 0; } /* sub rule flag check */ int is_effective_sub = 1; if (key_len == walk_args->full_key_len) { is_effective_sub = 0; } else if (key_len == walk_args->full_key_len - 1 && walk_args->full_key_len > 0) { is_effective_sub = 0; } if (walk_args->rule_index >= 0) { i = walk_args->rule_index; } else { i = 0; } for (; i < domain_rule->capacity; i++) { if (domain_rule->rules[i] == NULL) { if (walk_args->rule_index >= 0) { break; } continue; } if (i == DOMAIN_RULE_FLAGS) { struct dns_rule_flags *rule_flags = (struct dns_rule_flags *)domain_rule->rules[i]; if (rule_flags->head.sub_only == 1 && is_effective_sub == 0) { continue; } if (rule_flags->head.root_only == 1 && is_effective_sub == 1) { continue; } request_domain_rule->flags |= ((struct dns_rule_flags *)domain_rule->rules[i])->flags; } if (domain_rule->rules[i]->sub_only == 1 && is_effective_sub == 0) { continue; } if (domain_rule->rules[i]->root_only == 1 && is_effective_sub == 1) { continue; } request_domain_rule->rules[i] = domain_rule->rules[i]; request_domain_rule->is_sub_rule[i] = is_subkey; walk_args->key[i] = key; walk_args->key_len[i] = key_len; if (walk_args->rule_index >= 0) { break; } } /* update rules by flags */ _dns_server_update_rule_by_flags(request_domain_rule); return 0; } void _dns_server_get_domain_rule_by_domain_ext(struct dns_conf_group *conf, struct dns_request_domain_rule *request_domain_rule, int rule_index, const char *domain, int out_log) { int domain_len = 0; char domain_key[DNS_MAX_CNAME_LEN] = {0}; struct rule_walk_args walk_args; int matched_key_len = DNS_MAX_CNAME_LEN; unsigned char matched_key[DNS_MAX_CNAME_LEN] = {0}; int i = 0; memset(&walk_args, 0, sizeof(walk_args)); walk_args.args = request_domain_rule; walk_args.rule_index = rule_index; /* reverse domain string */ domain_len = strlen(domain); if (domain_len >= (int)sizeof(domain_key) - 3) { return; } reverse_string(domain_key + 1, domain, domain_len, 1); domain_key[domain_len + 1] = '.'; domain_key[0] = '.'; domain_len += 2; domain_key[domain_len] = 0; walk_args.full_key_len = domain_len; /* find domain rule */ art_substring_walk(&conf->domain_rule.tree, (unsigned char *)domain_key, domain_len, _dns_server_get_rules, &walk_args); if (likely(dns_conf.log_level > TLOG_DEBUG) || out_log == 0) { return; } if (walk_args.rule_index >= 0) { i = walk_args.rule_index; } else { i = 0; } /* output log rule */ for (; i < DOMAIN_RULE_MAX; i++) { if (walk_args.key[i] == NULL) { if (walk_args.rule_index >= 0) { break; } continue; } matched_key_len = walk_args.key_len[i]; if (walk_args.key_len[i] >= sizeof(matched_key)) { continue; } memcpy(matched_key, walk_args.key[i], walk_args.key_len[i]); matched_key_len--; matched_key[matched_key_len] = 0; _dns_server_log_rule(domain, i, matched_key, matched_key_len); if (walk_args.rule_index >= 0) { break; } } } void _dns_server_get_domain_rule(struct dns_request *request) { if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULES) == 0) { return; } _dns_server_get_domain_rule_by_domain(request, request->domain, 1); } int _dns_server_passthrough_rule_check(struct dns_request *request, const char *domain, struct dns_packet *packet, unsigned int result_flag, int *pttl) { int ttl = 0; char name[DNS_MAX_CNAME_LEN] = {0}; char cname[DNS_MAX_CNAME_LEN] = {0}; int rr_count = 0; int i = 0; int j = 0; struct dns_rrs *rrs = NULL; int ip_check_result = 0; if (packet->head.rcode != DNS_RC_NOERROR && packet->head.rcode != DNS_RC_NXDOMAIN) { if (request->rcode == DNS_RC_SERVFAIL) { request->rcode = packet->head.rcode; request->remote_server_fail = 1; } tlog(TLOG_DEBUG, "inquery failed, %s, rcode = %d, id = %d\n", domain, packet->head.rcode, packet->head.id); return 0; } for (j = 1; j < DNS_RRS_OPT; j++) { rrs = dns_get_rrs_start(packet, j, &rr_count); for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { switch (rrs->type) { case DNS_T_A: { unsigned char addr[4]; int ttl_tmp = 0; if (request->qtype != DNS_T_A) { /* ignore non-matched query type */ if (request->dualstack_selection == 0) { break; } } _dns_server_request_get(request); /* get A result */ dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl_tmp, addr); /* if domain is not match */ if (strncasecmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncasecmp(cname, name, DNS_MAX_CNAME_LEN) != 0) { _dns_server_request_release(request); continue; } tlog(TLOG_DEBUG, "domain: %s TTL: %d IP: %d.%d.%d.%d", name, ttl_tmp, addr[0], addr[1], addr[2], addr[3]); /* ip rule check */ ip_check_result = _dns_server_process_ip_rule(request, addr, 4, DNS_T_A, result_flag, NULL); if (ip_check_result == 0 || ip_check_result == -2 || ip_check_result == -3) { /* match, skip, nxdomain */ _dns_server_request_release(request); return 0; } /* Ad blocking result */ if (addr[0] == 0 || addr[0] == 127) { /* If half of the servers return the same result, then ignore this address */ if (atomic_read(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) { _dns_server_request_release(request); return 0; } } ttl = _dns_server_get_conf_ttl(request, ttl_tmp); _dns_server_request_release(request); } break; case DNS_T_AAAA: { unsigned char addr[16]; int ttl_tmp = 0; if (request->qtype != DNS_T_AAAA) { /* ignore non-matched query type */ break; } _dns_server_request_get(request); dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl_tmp, addr); /* if domain is not match */ if (strncasecmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncasecmp(cname, name, DNS_MAX_CNAME_LEN) != 0) { _dns_server_request_release(request); continue; } tlog(TLOG_DEBUG, "domain: %s TTL: %d IP: " "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", name, ttl_tmp, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]); ip_check_result = _dns_server_process_ip_rule(request, addr, 16, DNS_T_AAAA, result_flag, NULL); if (ip_check_result == 0 || ip_check_result == -2 || ip_check_result == -3) { /* match, skip, nxdomain */ _dns_server_request_release(request); return 0; } /* Ad blocking result */ if (_dns_server_is_adblock_ipv6(addr) == 0) { /* If half of the servers return the same result, then ignore this address */ if (atomic_read(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) { _dns_server_request_release(request); return 0; } } ttl = _dns_server_get_conf_ttl(request, ttl_tmp); _dns_server_request_release(request); } break; case DNS_T_CNAME: { dns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN); } break; case DNS_T_HTTPS: { struct dns_https_record_rule *https_record_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_HTTPS); if (https_record_rule) { if (https_record_rule->filter.no_ipv4hint || https_record_rule->filter.no_ipv6hint || https_record_rule->filter.no_ech) { /* Need to filter, do not passthrough */ return 0; } } } break; default: if (ttl == 0) { /* Get TTL */ char tmpname[DNS_MAX_CNAME_LEN] = {0}; char tmpbuf[DNS_MAX_CNAME_LEN] = {0}; dns_get_CNAME(rrs, tmpname, DNS_MAX_CNAME_LEN, &ttl, tmpbuf, DNS_MAX_CNAME_LEN); if (request->ip_ttl == 0) { request->ip_ttl = _dns_server_get_conf_ttl(request, ttl); } } break; } } } request->remote_server_fail = 0; if (request->rcode == DNS_RC_SERVFAIL) { request->rcode = packet->head.rcode; } *pttl = ttl; return -1; } int _dns_server_get_conf_ttl(struct dns_request *request, int ttl) { int rr_ttl = request->conf->dns_rr_ttl; int rr_ttl_min = request->conf->dns_rr_ttl_min; int rr_ttl_max = request->conf->dns_rr_ttl_max; if (request->is_mdns_lookup) { rr_ttl_min = DNS_SERVER_ADDR_TTL; } struct dns_ttl_rule *ttl_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_TTL); if (ttl_rule != NULL) { if (ttl_rule->ttl > 0) { rr_ttl = ttl_rule->ttl; } /* make domain rule ttl high priority */ if (ttl_rule->ttl_min > 0) { rr_ttl_min = ttl_rule->ttl_min; if (request->conf->dns_rr_ttl_max <= rr_ttl_min && request->conf->dns_rr_ttl_max > 0) { rr_ttl_max = rr_ttl_min; } } if (ttl_rule->ttl_max > 0) { rr_ttl_max = ttl_rule->ttl_max; if (request->conf->dns_rr_ttl_min >= rr_ttl_max && request->conf->dns_rr_ttl_min > 0 && ttl_rule->ttl_min <= 0) { rr_ttl_min = rr_ttl_max; } } } if (rr_ttl > 0) { return rr_ttl; } /* make rr_ttl_min first priority */ if (rr_ttl_max < rr_ttl_min && rr_ttl_max > 0) { rr_ttl_max = rr_ttl_min; } if (rr_ttl_max > 0 && ttl >= rr_ttl_max) { ttl = rr_ttl_max; } else if (rr_ttl_min > 0 && ttl <= rr_ttl_min) { ttl = rr_ttl_min; } return ttl; } int _dns_server_get_reply_ttl(struct dns_request *request, int ttl) { int reply_ttl = ttl; if ((request->passthrough == 0 || request->passthrough == 2) && dns_conf.cachesize > 0 && request->check_order_list->orders[0].type != DOMAIN_CHECK_NONE && request->no_serve_expired == 0 && request->has_soa == 0 && request->no_cache == 0) { reply_ttl = request->conf->dns_serve_expired_reply_ttl; if (reply_ttl < 2) { reply_ttl = 2; } } int rr_ttl = _dns_server_get_conf_ttl(request, ttl); if (reply_ttl > rr_ttl) { reply_ttl = rr_ttl; } return reply_ttl; } void *_dns_server_get_dns_rule(struct dns_request *request, enum domain_rule rule) { if (request == NULL) { return NULL; } return _dns_server_get_dns_rule_ext(&request->domain_rule, rule); } uint32_t _dns_server_get_rule_flags(struct dns_request *request) { struct dns_rule_flags *rule_flag = NULL; if (request == NULL) { return 0; } if (request->domain_rule.flags != 0) { return request->domain_rule.flags; } if (request->domain_rule.rules[DOMAIN_RULE_FLAGS] == NULL) { return 0; } rule_flag = (struct dns_rule_flags *)request->domain_rule.rules[DOMAIN_RULE_FLAGS]; return rule_flag->flags; } int _dns_server_is_dns_rule_extract_match(struct dns_request *request, enum domain_rule rule) { if (request == NULL) { return 0; } return _dns_server_is_dns_rule_extract_match_ext(&request->domain_rule, rule); } int _dns_server_pre_process_rule_flags(struct dns_request *request) { /* get domain rule flag */ unsigned int flags = _dns_server_get_rule_flags(request); int rcode = DNS_RC_NOERROR; if (flags & DOMAIN_FLAG_NO_SERVE_EXPIRED) { request->no_serve_expired = 1; } if (flags & DOMAIN_FLAG_NO_CACHE) { request->no_cache = 1; } if (flags & DOMAIN_FLAG_ENABLE_CACHE) { request->no_cache = 0; } if (flags & DOMAIN_FLAG_NO_IPALIAS) { request->no_ipalias = 1; } if (flags & DOMAIN_FLAG_ADDR_IGN) { /* ignore this domain */ goto skip_soa_out; } /* return specific type of address */ switch (request->qtype) { case DNS_T_A: if (flags & DOMAIN_FLAG_ADDR_IPV4_IGN) { /* ignore this domain for A request */ goto skip_soa_out; } if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] != NULL) { goto skip_soa_out; } if (_dns_server_is_return_soa(request)) { /* if AAAA exists, return SOA with NOERROR*/ if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] != NULL) { goto soa; } /* if AAAA not exists, return SOA with NXDOMAIN */ if (_dns_server_is_return_soa_qtype(request, DNS_T_AAAA)) { rcode = DNS_RC_NXDOMAIN; } goto soa; } goto out; break; case DNS_T_AAAA: if (flags & DOMAIN_FLAG_ADDR_IPV6_IGN) { /* ignore this domain for A request */ goto skip_soa_out; } if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] != NULL) { goto skip_soa_out; } if (_dns_server_is_return_soa(request)) { /* if A exists, return SOA with NOERROR*/ if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] != NULL) { goto soa; } /* if A not exists, return SOA with NXDOMAIN */ if (_dns_server_is_return_soa_qtype(request, DNS_T_A)) { rcode = DNS_RC_NXDOMAIN; } goto soa; } if (flags & DOMAIN_FLAG_ADDR_IPV4_SOA && request->dualstack_selection) { /* if IPV4 return SOA and dualstack-selection enabled, set request dualstack disable */ request->dualstack_selection = 0; } goto out; break; case DNS_T_HTTPS: if (flags & DOMAIN_FLAG_ADDR_HTTPS_IGN) { /* ignore this domain for A request */ goto skip_soa_out; } if (_dns_server_is_return_soa(request)) { /* if HTTPS exists, return SOA with NOERROR*/ if (request->domain_rule.rules[DOMAIN_RULE_HTTPS] != NULL) { goto soa; } if (_dns_server_is_return_soa_qtype(request, DNS_T_A) && _dns_server_is_return_soa_qtype(request, DNS_T_AAAA)) { /* return SOA for HTTPS request */ rcode = DNS_RC_NXDOMAIN; goto soa; } } if (request->domain_rule.rules[DOMAIN_RULE_HTTPS] != NULL) { goto skip_soa_out; } goto out; break; default: goto out; break; } if (_dns_server_is_return_soa(request)) { goto soa; } skip_soa_out: request->skip_qtype_soa = 1; out: return -1; soa: /* return SOA */ _dns_server_reply_SOA(rcode, request); return 0; } void _dns_server_process_speed_rule(struct dns_request *request) { struct dns_domain_check_orders *check_order = NULL; struct dns_response_mode_rule *response_mode = NULL; /* get speed check mode */ check_order = _dns_server_get_dns_rule(request, DOMAIN_RULE_CHECKSPEED); if (check_order != NULL) { request->check_order_list = check_order; } /* get response mode */ response_mode = _dns_server_get_dns_rule(request, DOMAIN_RULE_RESPONSE_MODE); if (response_mode != NULL) { request->response_mode = response_mode->mode; } else { request->response_mode = request->conf->dns_response_mode; } } void _dns_server_get_domain_rule_by_domain(struct dns_request *request, const char *domain, int out_log) { if (request->skip_domain_rule != 0) { return; } if (request->conf == NULL) { return; } _dns_server_get_domain_rule_by_domain_ext(request->conf, &request->domain_rule, -1, domain, out_log); request->skip_domain_rule = 1; } ================================================ FILE: src/dns_server/rules.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_RULES_ #define _DNS_SERVER_RULES_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void *_dns_server_get_dns_rule(struct dns_request *request, enum domain_rule rule); uint32_t _dns_server_get_rule_flags(struct dns_request *request); int _dns_server_get_conf_ttl(struct dns_request *request, int ttl); void *_dns_server_get_dns_rule_ext(struct dns_request_domain_rule *domain_rule, enum domain_rule rule); void _dns_server_get_domain_rule(struct dns_request *request); int _dns_server_pre_process_rule_flags(struct dns_request *request); int _dns_server_get_reply_ttl(struct dns_request *request, int ttl); int _dns_server_is_dns_rule_extract_match(struct dns_request *request, enum domain_rule rule); void _dns_server_get_domain_rule_by_domain_ext(struct dns_conf_group *conf, struct dns_request_domain_rule *request_domain_rule, int rule_index, const char *domain, int out_log); int _dns_server_passthrough_rule_check(struct dns_request *request, const char *domain, struct dns_packet *packet, unsigned int result_flag, int *pttl); void _dns_server_process_speed_rule(struct dns_request *request); void _dns_server_get_domain_rule_by_domain(struct dns_request *request, const char *domain, int out_log); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/server_http2.c ================================================ /************************************************************************* * * 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 . * *************************************************************************/ #include "server_http2.h" #include "connection.h" #include "dns_server.h" #include "server_tls.h" #include "smartdns/dns_conf.h" #include "smartdns/http2.h" #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #include #include #define DNS_SERVER_HTTP2_MAX_CONCURRENT_STREAMS 4096 static int _http2_server_bio_read(void *private_data, uint8_t *buf, int len) { struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)private_data; return _dns_server_socket_ssl_recv(tls_client, buf, len); } static int _http2_server_bio_write(void *private_data, const uint8_t *buf, int len) { struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)private_data; return _dns_server_socket_ssl_send(tls_client, buf, len); } static int _dns_server_http2_send_response(struct http2_stream *stream, int status, const char *content_type, const void *body, int body_len) { char content_length[32]; snprintf(content_length, sizeof(content_length), "%d", body_len); struct http2_header_pair headers[] = { {"content-type", content_type}, {"content-length", content_length}, {NULL, NULL}}; if (http2_stream_set_response(stream, status, headers, 2) < 0) { return -1; } if (http2_stream_write_body(stream, (const uint8_t *)body, body_len, 1) < 0) { return -1; } return 0; } int _dns_server_reply_http2(struct dns_request *request, struct dns_server_conn_http2_stream *stream_conn, unsigned char *inpacket, int inpacket_len) { struct http2_stream *stream = stream_conn->stream; if (stream == NULL) { return -1; } /* Send DNS response */ /* Content-Type for DoH is application/dns-message */ return _dns_server_http2_send_response(stream, 200, "application/dns-message", inpacket, inpacket_len); } static void _dns_server_http2_process_stream(struct dns_server_conn_tls_client *tls_client, struct http2_stream *stream) { uint8_t buf[DNS_IN_PACKSIZE]; int len = 0; const char *method = http2_stream_get_method(stream); if (method == NULL) { return; } if (strcasecmp(method, "POST") == 0) { /* Read request body */ len = http2_stream_read_body(stream, buf, sizeof(buf)); if (len < 0) { /* Error or no data yet */ if (http2_stream_is_end(stream)) { goto close_out; } return; } if (len == 0 && !http2_stream_is_end(stream)) { /* No data available but stream not ended */ return; } } else if (strcasecmp(method, "GET") == 0) { const char *path = http2_stream_get_path(stream); char *base64_query = NULL; if (http2_stream_get_ex_data(stream)) { goto close_out; } http2_stream_set_ex_data(stream, (void *)1); /* Consume any body (should be empty for GET) to mark stream as read-handled */ http2_stream_read_body(stream, NULL, 0); if (path == NULL) { _dns_server_http2_send_response(stream, 404, "text/plain", "Not Found", 9); goto close_out; } /* Check path prefix */ if (strncmp(path, "/dns-query", 10) != 0) { _dns_server_http2_send_response(stream, 404, "text/plain", "Not Found", 9); goto close_out; } /* Parse query string */ char *query_val = http2_stream_get_query_param(stream, "dns"); if (query_val == NULL) { _dns_server_http2_send_response(stream, 400, "text/plain", "Bad Request", 11); goto close_out; } base64_query = malloc(DNS_IN_PACKSIZE); if (base64_query == NULL) { free(query_val); _dns_server_http2_send_response(stream, 500, "text/plain", "Bad Request", 11); goto close_out; } if (urldecode(base64_query, DNS_IN_PACKSIZE, query_val) < 0) { free(query_val); free(base64_query); _dns_server_http2_send_response(stream, 400, "text/plain", "Bad Request", 11); goto close_out; } free(query_val); len = SSL_base64_decode_ext(base64_query, buf, sizeof(buf), 1, 1); free(base64_query); if (len <= 0) { _dns_server_http2_send_response(stream, 400, "text/plain", "Bad Request", 11); goto close_out; } } else { _dns_server_http2_send_response(stream, 405, "text/plain", "Method Not Allowed", 18); goto close_out; } if (len > 0) { /* Create a fake connection object for this stream */ struct dns_server_conn_http2_stream *stream_conn = zalloc(1, sizeof(struct dns_server_conn_http2_stream)); if (stream_conn == NULL) { _dns_server_http2_send_response(stream, 500, "text/plain", "Bad Request", 11); goto close_out; } /* Initialize the fake connection */ _dns_server_conn_head_init(&stream_conn->head, -1, DNS_CONN_TYPE_HTTP2_STREAM); stream_conn->stream = http2_stream_get(stream); stream_conn->tls_client = tls_client; /* Copy properties from parent connection */ stream_conn->head.server_flags = tls_client->tcp.head.server_flags; stream_conn->head.dns_group = tls_client->tcp.head.dns_group; stream_conn->head.ipset_nftset_rule = tls_client->tcp.head.ipset_nftset_rule; /* We need to increment refcnt because _dns_server_recv (via request) will eventually release it */ _dns_server_conn_get(&stream_conn->head); /* Process the packet */ /* Note: _dns_server_recv takes conn, inpacket, inpacket_len, local, local_len, from, from_len */ _dns_server_recv(&stream_conn->head, buf, len, &tls_client->tcp.localaddr, tls_client->tcp.localaddr_len, &tls_client->tcp.addr, tls_client->tcp.addr_len); /* Release our reference (request holds one now) */ _dns_server_conn_release(&stream_conn->head); } return; close_out: if (stream != NULL) { /* Close stream on error */ http2_stream_get(stream); http2_stream_close(stream); } } int _dns_server_process_http2(struct dns_server_conn_tls_client *tls_client, struct epoll_event *event, unsigned long now) { struct http2_ctx *ctx = (struct http2_ctx *)tls_client->http2_ctx; int ret = 0; /* Initialize HTTP/2 context if not already done */ if (ctx == NULL) { struct http2_settings settings; memset(&settings, 0, sizeof(settings)); settings.max_concurrent_streams = DNS_SERVER_HTTP2_MAX_CONCURRENT_STREAMS; ctx = http2_ctx_server_new("smartdns-server", _http2_server_bio_read, _http2_server_bio_write, tls_client, &settings); if (ctx == NULL) { tlog(TLOG_ERROR, "init http2 context failed."); return -1; } /* Perform initial handshake */ ret = http2_ctx_handshake(ctx); if (ret < 0) { const char *err_msg = http2_error_to_string(ret); int log_level = TLOG_ERROR; if (ret == HTTP2_ERR_EOF || ret == HTTP2_ERR_HTTP1) { log_level = TLOG_DEBUG; /* Less noisy for clients that disconnect early or misbehave */ } tlog(log_level, "http2 handshake failed, ret=%d (%s), alpn=%s.", ret, err_msg, tls_client->alpn_selected); http2_ctx_close(ctx); return -1; } tls_client->http2_ctx = ctx; } /* Handle EPOLLOUT - flush pending writes */ if (event->events & EPOLLOUT) { int loop = 0; while (http2_ctx_want_write(ctx) && loop++ < 10) { if (http2_ctx_poll(ctx, NULL, 0, NULL) < 0) { break; } } } /* Handle EPOLLIN - read and process data */ if (event->events & EPOLLIN) { struct http2_poll_item poll_items[10]; int poll_count = 0; int loop_count = 0; const int MAX_LOOP_COUNT = 512; /* Ensure handshake is complete */ ret = http2_ctx_handshake(ctx); if (ret < 0) { const char *err_msg = http2_error_to_string(ret); int log_level = TLOG_ERROR; if (ret == HTTP2_ERR_EOF || ret == HTTP2_ERR_HTTP1) { log_level = TLOG_DEBUG; /* Less noisy for clients that disconnect early or misbehave */ } tlog(log_level, "http2 handshake failed, ret=%d (%s), alpn=%s.", ret, err_msg, tls_client->alpn_selected); return -1; } else if (ret == 0) { /* Handshake in progress */ goto update_epoll; } /* Poll and process */ while (loop_count++ < MAX_LOOP_COUNT) { poll_count = 0; ret = http2_ctx_poll_readable(ctx, poll_items, 10, &poll_count); if (ret < 0) { if (ret == HTTP2_ERR_EAGAIN) { break; } if (ret == HTTP2_ERR_EOF) { /* Connection closed by peer */ _dns_server_client_close(&tls_client->tcp.head); return 0; } tlog(TLOG_DEBUG, "http2 poll failed, %s", http2_error_to_string(ret)); return -1; } if (poll_count == 0) { continue; } for (int i = 0; i < poll_count; i++) { if (poll_items[i].stream == NULL) { if (poll_items[i].readable) { struct http2_stream *stream = http2_ctx_accept_stream(ctx); if (stream) { /* Accept and immediately process new HTTP/2 stream */ _dns_server_http2_process_stream(tls_client, stream); http2_stream_put(stream); } } continue; } if (poll_items[i].stream && poll_items[i].readable) { _dns_server_http2_process_stream(tls_client, poll_items[i].stream); } if (poll_items[i].stream) { http2_stream_put(poll_items[i].stream); /* Release poll reference */ } } } } update_epoll: /* Update epoll events */ { int epoll_events = EPOLLIN; if (http2_ctx_want_write(ctx)) { epoll_events |= EPOLLOUT; } struct epoll_event mod_event; memset(&mod_event, 0, sizeof(mod_event)); mod_event.events = epoll_events; mod_event.data.ptr = tls_client; if (epoll_ctl(server.epoll_fd, EPOLL_CTL_MOD, tls_client->tcp.head.fd, &mod_event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); return -1; } } return 0; } ================================================ FILE: src/dns_server/server_http2.h ================================================ /************************************************************************* * * 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 . * *************************************************************************/ #ifndef _SERVER_HTTP2_H_ #define _SERVER_HTTP2_H_ #include "dns_server.h" #include "smartdns/http2.h" #include #ifdef __cplusplus extern "C" { #endif struct dns_server_conn_http2_stream { struct dns_server_conn_head head; struct http2_stream *stream; struct dns_server_conn_tls_client *tls_client; }; int _dns_server_process_http2(struct dns_server_conn_tls_client *tls_client, struct epoll_event *event, unsigned long now); int _dns_server_reply_http2(struct dns_request *request, struct dns_server_conn_http2_stream *stream_conn, unsigned char *inpacket, int inpacket_len); #ifdef __cplusplus } #endif #endif ================================================ FILE: src/dns_server/server_https.c ================================================ /************************************************************************* * * 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 . */ #include "server_https.h" #include "connection.h" #include "dns_server.h" #include "server_socket.h" #include "server_tcp.h" #include "server_tls.h" #include "smartdns/http2.h" #include #include int _dns_server_reply_http_error(struct dns_server_conn_tcp_client *tcpclient, int code, const char *code_msg, const char *message) { int send_len = 0; int http_len = 0; unsigned char data[DNS_IN_PACKSIZE]; int msg_len = strlen(message); http_len = snprintf((char *)data, DNS_IN_PACKSIZE, "HTTP/1.1 %d %s\r\n" "Content-Length: %d\r\n" "\r\n" "%s\r\n", code, code_msg, msg_len + 2, message); send_len = _dns_server_tcp_socket_send(tcpclient, data, http_len); if (send_len < 0) { if (errno == EAGAIN) { /* save data to buffer, and retry when EPOLLOUT is available */ return _dns_server_reply_tcp_to_buffer(tcpclient, data, http_len); } return -1; } else if (send_len < http_len) { /* save remain data to buffer, and retry when EPOLLOUT is available */ return _dns_server_reply_tcp_to_buffer(tcpclient, data + send_len, http_len - send_len); } return 0; } int _dns_server_reply_https(struct dns_request *request, struct dns_server_conn_tcp_client *tcpclient, void *packet, unsigned short len) { int send_len = 0; int http_len = 0; unsigned char inpacket_data[DNS_IN_PACKSIZE]; unsigned char *inpacket = inpacket_data; if (len > sizeof(inpacket_data)) { tlog(TLOG_ERROR, "packet size is invalid."); return -1; } http_len = snprintf((char *)inpacket, DNS_IN_PACKSIZE, "HTTP/1.1 200 OK\r\n" "Content-Type: application/dns-message\r\n" "Content-Length: %d\r\n" "\r\n", len); if (http_len < 0 || http_len >= DNS_IN_PACKSIZE) { tlog(TLOG_ERROR, "http header size is invalid."); return -1; } memcpy(inpacket + http_len, packet, len); http_len += len; send_len = _dns_server_tcp_socket_send(tcpclient, inpacket, http_len); if (send_len < 0) { if (errno == EAGAIN) { /* save data to buffer, and retry when EPOLLOUT is available */ return _dns_server_reply_tcp_to_buffer(tcpclient, inpacket, http_len); } return -1; } else if (send_len < http_len) { /* save remain data to buffer, and retry when EPOLLOUT is available */ return _dns_server_reply_tcp_to_buffer(tcpclient, inpacket + send_len, http_len - send_len); } return 0; } ================================================ FILE: src/dns_server/server_https.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_HTTPS_ #define _DNS_SERVER_HTTPS_ #include "dns_server.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_reply_http_error(struct dns_server_conn_tcp_client *tcpclient, int code, const char *code_msg, const char *message); int _dns_server_reply_https(struct dns_request *request, struct dns_server_conn_tcp_client *tcpclient, void *packet, unsigned short len); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/server_socket.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "server_socket.h" #include "dns_server.h" #include #include #include #include #include #include #include #include #include #include #include #include static struct addrinfo *_dns_server_getaddr(const char *host, const char *port, int type, int protocol) { struct addrinfo hints; struct addrinfo *result = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = type; hints.ai_protocol = protocol; hints.ai_flags = AI_PASSIVE; const int s = getaddrinfo(host, port, &hints, &result); if (s != 0) { const char *error_str; if (s == EAI_SYSTEM) { error_str = strerror(errno); } else { error_str = gai_strerror(s); } tlog(TLOG_ERROR, "get addr info failed. %s.\n", error_str); goto errout; } return result; errout: if (result) { freeaddrinfo(result); } return NULL; } int _dns_create_socket(const char *host_ip, int type) { int fd = -1; struct addrinfo *gai = NULL; char port_str[16]; char ip[MAX_IP_LEN]; char host_ip_device[MAX_IP_LEN * 2]; int port = 0; char *host = NULL; int optval = 1; int yes = 1; const int priority = SOCKET_PRIORITY; const int ip_tos = SOCKET_IP_TOS; const char *ifname = NULL; safe_strncpy(host_ip_device, host_ip, sizeof(host_ip_device)); ifname = strstr(host_ip_device, "@"); if (ifname) { *(char *)ifname = '\0'; ifname++; } if (parse_ip(host_ip_device, ip, &port) == 0) { host = ip; } if (port <= 0) { port = DEFAULT_DNS_PORT; } snprintf(port_str, sizeof(port_str), "%d", port); gai = _dns_server_getaddr(host, port_str, type, 0); if (gai == NULL) { tlog(TLOG_ERROR, "get address failed."); goto errout; } fd = socket(gai->ai_family, gai->ai_socktype, gai->ai_protocol); if (fd < 0) { tlog(TLOG_ERROR, "create socket failed, family = %d, type = %d, proto = %d, %s\n", gai->ai_family, gai->ai_socktype, gai->ai_protocol, strerror(errno)); goto errout; } if (type == SOCK_STREAM) { if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) != 0) { tlog(TLOG_ERROR, "set socket opt failed."); goto errout; } /* enable TCP_FASTOPEN */ setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &optval, sizeof(optval)); setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); } else { setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &optval, sizeof(optval)); setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &optval, sizeof(optval)); } setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); if (dns_conf.dns_socket_buff_size > 0) { setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size)); } if (ifname != NULL) { struct ifreq ifr; memset(&ifr, 0, sizeof(struct ifreq)); safe_strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); ioctl(fd, SIOCGIFINDEX, &ifr); if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) { tlog(TLOG_ERROR, "bind socket to device %s failed, %s\n", ifr.ifr_name, strerror(errno)); goto errout; } } if (bind(fd, gai->ai_addr, gai->ai_addrlen) != 0) { tlog(TLOG_ERROR, "bind service %s failed, %s\n", host_ip, strerror(errno)); goto errout; } if (type == SOCK_STREAM) { if (listen(fd, 256) != 0) { tlog(TLOG_ERROR, "listen failed.\n"); goto errout; } } fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); freeaddrinfo(gai); return fd; errout: if (fd > 0) { close(fd); } if (gai) { freeaddrinfo(gai); } tlog(TLOG_ERROR, "add server failed, host-ip: %s, type: %d", host_ip, type); return -1; } ================================================ FILE: src/dns_server/server_socket.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_SOCKET_ #define _DNS_SERVER_SOCKET_ #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_create_socket(const char *host_ip, int type); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/server_tcp.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "server_tcp.h" #include "connection.h" #include "dns_server.h" #include "server_https.h" #include "server_socket.h" #include "server_tls.h" #include "smartdns/http_parse.h" #include #include #include #include int _dns_server_reply_tcp_to_buffer(struct dns_server_conn_tcp_client *tcpclient, void *packet, int len) { if ((int)sizeof(tcpclient->sndbuff.buf) - tcpclient->sndbuff.size < len) { return -1; } memcpy(tcpclient->sndbuff.buf + tcpclient->sndbuff.size, packet, len); tcpclient->sndbuff.size += len; if (tcpclient->head.fd <= 0) { return -1; } if (_dns_server_epoll_ctl(&tcpclient->head, EPOLL_CTL_MOD, EPOLLIN | EPOLLOUT) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); return -1; } return 0; } int _dns_server_reply_tcp(struct dns_request *request, struct dns_server_conn_tcp_client *tcpclient, void *packet, unsigned short len) { int send_len = 0; unsigned char inpacket_data[DNS_IN_PACKSIZE]; unsigned char *inpacket = inpacket_data; if (len > sizeof(inpacket_data) - 2) { tlog(TLOG_ERROR, "packet size is invalid."); return -1; } /* TCP query format * | len (short) | dns query data | */ *((unsigned short *)(inpacket)) = htons(len); memcpy(inpacket + 2, packet, len); len += 2; send_len = _dns_server_tcp_socket_send(tcpclient, inpacket, len); if (send_len < 0) { if (errno == EAGAIN) { /* save data to buffer, and retry when EPOLLOUT is available */ return _dns_server_reply_tcp_to_buffer(tcpclient, inpacket, len); } return -1; } else if (send_len < len) { /* save remain data to buffer, and retry when EPOLLOUT is available */ return _dns_server_reply_tcp_to_buffer(tcpclient, inpacket + send_len, len - send_len); } return 0; } int _dns_server_tcp_accept(struct dns_server_conn_tcp_server *tcpserver, struct epoll_event *event, unsigned long now) { struct sockaddr_storage addr; struct dns_server_conn_tcp_client *tcpclient = NULL; socklen_t addr_len = sizeof(addr); int fd = -1; fd = accept4(tcpserver->head.fd, (struct sockaddr *)&addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC); if (fd < 0) { tlog(TLOG_ERROR, "accept failed, %s", strerror(errno)); return -1; } tcpclient = zalloc(1, sizeof(*tcpclient)); if (tcpclient == NULL) { tlog(TLOG_ERROR, "malloc for tcpclient failed."); goto errout; } _dns_server_conn_head_init(&tcpclient->head, fd, DNS_CONN_TYPE_TCP_CLIENT); tcpclient->head.server_flags = tcpserver->head.server_flags; tcpclient->head.dns_group = tcpserver->head.dns_group; tcpclient->head.ipset_nftset_rule = tcpserver->head.ipset_nftset_rule; tcpclient->conn_idle_timeout = dns_conf.tcp_idle_time; memcpy(&tcpclient->addr, &addr, addr_len); tcpclient->addr_len = addr_len; tcpclient->localaddr_len = sizeof(struct sockaddr_storage); if (_dns_server_epoll_ctl(&tcpclient->head, EPOLL_CTL_ADD, EPOLLIN) != 0) { tlog(TLOG_ERROR, "epoll ctl failed."); return -1; } if (getsocket_inet(tcpclient->head.fd, (struct sockaddr *)&tcpclient->localaddr, &tcpclient->localaddr_len) != 0) { tlog(TLOG_ERROR, "get local addr failed, %s", strerror(errno)); goto errout; } _dns_server_client_touch(&tcpclient->head); pthread_mutex_lock(&server.conn_list_lock); list_add(&tcpclient->head.list, &server.conn_list); pthread_mutex_unlock(&server.conn_list_lock); _dns_server_conn_get(&tcpclient->head); set_sock_keepalive(fd, 30, 3, 5); return 0; errout: if (fd > 0) { close(fd); } if (tcpclient) { free(tcpclient); } return -1; } int _dns_server_tcp_socket_send(struct dns_server_conn_tcp_client *tcp_client, void *data, int data_len) { if (tcp_client->head.type == DNS_CONN_TYPE_TCP_CLIENT) { return send(tcp_client->head.fd, data, data_len, MSG_NOSIGNAL); } else if (tcp_client->head.type == DNS_CONN_TYPE_TLS_CLIENT || tcp_client->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) { struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)tcp_client; tls_client->ssl_want_write = 0; int ret = _dns_server_socket_ssl_send(tls_client, data, data_len); if (ret < 0 && errno == EAGAIN) { if (_dns_server_ssl_poll_event(tls_client, SSL_ERROR_WANT_WRITE) == 0) { errno = EAGAIN; } } return ret; } else { return -1; } } int _dns_server_tcp_socket_recv(struct dns_server_conn_tcp_client *tcp_client, void *data, int data_len) { if (tcp_client->head.type == DNS_CONN_TYPE_TCP_CLIENT) { return recv(tcp_client->head.fd, data, data_len, MSG_NOSIGNAL); } else if (tcp_client->head.type == DNS_CONN_TYPE_TLS_CLIENT || tcp_client->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) { struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)tcp_client; int ret = _dns_server_socket_ssl_recv(tls_client, data, data_len); if (ret == -SSL_ERROR_WANT_WRITE && errno == EAGAIN) { if (_dns_server_ssl_poll_event(tls_client, SSL_ERROR_WANT_WRITE) == 0) { errno = EAGAIN; tls_client->ssl_want_write = 1; } } return ret; } else { return -1; } } static int _dns_server_tcp_recv(struct dns_server_conn_tcp_client *tcpclient) { ssize_t len = 0; /* Receive data */ while (tcpclient->recvbuff.size < (int)sizeof(tcpclient->recvbuff.buf)) { if (tcpclient->recvbuff.size == (int)sizeof(tcpclient->recvbuff.buf)) { return 0; } if (unlikely(tcpclient->recvbuff.size < 0)) { BUG("recv buffer size is invalid."); } len = _dns_server_tcp_socket_recv(tcpclient, tcpclient->recvbuff.buf + tcpclient->recvbuff.size, sizeof(tcpclient->recvbuff.buf) - tcpclient->recvbuff.size); if (len < 0) { if (errno == EAGAIN) { return RECV_ERROR_AGAIN; } if (errno == ECONNRESET) { return RECV_ERROR_CLOSE; } if (errno == ETIMEDOUT) { return RECV_ERROR_CLOSE; } tlog(TLOG_DEBUG, "recv failed, %s\n", strerror(errno)); return RECV_ERROR_FAIL; } else if (len == 0) { return RECV_ERROR_CLOSE; } tcpclient->recvbuff.size += len; } return 0; } static int _dns_server_tcp_process_one_request(struct dns_server_conn_tcp_client *tcpclient) { unsigned short request_len = 0; int total_len = tcpclient->recvbuff.size; int proceed_len = 0; unsigned char *request_data = NULL; int ret = RECV_ERROR_FAIL; int len = 0; struct http_head *http_head = NULL; uint8_t *http_decode_data = NULL; char *base64_query = NULL; /* Handling multiple requests */ for (;;) { ret = RECV_ERROR_FAIL; if (proceed_len > tcpclient->recvbuff.size) { tlog(TLOG_DEBUG, "proceed_len > recvbuff.size"); goto out; } if (tcpclient->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) { if ((total_len - proceed_len) <= 0) { ret = RECV_ERROR_AGAIN; goto out; } http_head = http_head_init(4096, HTTP_VERSION_1_1); if (http_head == NULL) { goto out; } len = http_head_parse(http_head, tcpclient->recvbuff.buf + proceed_len, tcpclient->recvbuff.size - proceed_len); if (len < 0) { if (len == -1) { ret = 0; goto out; } else if (len == -3) { tcpclient->recvbuff.size = 0; tlog(TLOG_DEBUG, "recv buffer is not enough."); goto errout; } tlog(TLOG_DEBUG, "parser http header failed."); goto errout; } if (http_head_get_method(http_head) == HTTP_METHOD_POST) { const char *content_type = http_head_get_fields_value(http_head, "Content-Type"); if (content_type == NULL || strncasecmp(content_type, "application/dns-message", sizeof("application/dns-message")) != 0) { tlog(TLOG_DEBUG, "content type not supported, %s", content_type); goto errout; } request_len = http_head_get_data_len(http_head); if (request_len >= len) { tlog(TLOG_DEBUG, "request length is invalid."); goto errout; } request_data = (unsigned char *)http_head_get_data(http_head); } else if (http_head_get_method(http_head) == HTTP_METHOD_GET) { const char *path = http_head_get_url(http_head); if (path == NULL || strncasecmp(path, "/dns-query", sizeof("/dns-query")) != 0) { ret = RECV_ERROR_BAD_PATH; tlog(TLOG_DEBUG, "path not supported, %s", path); goto errout; } const char *dns_query = http_head_get_params_value(http_head, "dns"); if (dns_query == NULL) { tlog(TLOG_DEBUG, "query is null."); goto errout; } if (base64_query == NULL) { base64_query = malloc(DNS_IN_PACKSIZE); if (base64_query == NULL) { tlog(TLOG_DEBUG, "malloc failed."); goto errout; } } if (urldecode(base64_query, DNS_IN_PACKSIZE, dns_query) < 0) { tlog(TLOG_DEBUG, "urldecode query failed."); goto errout; } if (http_decode_data == NULL) { http_decode_data = malloc(DNS_IN_PACKSIZE); if (http_decode_data == NULL) { tlog(TLOG_DEBUG, "malloc failed."); goto errout; } } int decode_len = SSL_base64_decode_ext(base64_query, http_decode_data, DNS_IN_PACKSIZE, 1, 1); if (decode_len <= 0) { tlog(TLOG_DEBUG, "decode query failed."); goto errout; } request_len = decode_len; request_data = http_decode_data; } else { tlog(TLOG_DEBUG, "http method is invalid."); goto errout; } proceed_len += len; } else { if ((total_len - proceed_len) <= (int)sizeof(unsigned short)) { ret = RECV_ERROR_AGAIN; goto out; } /* Get record length */ request_data = (unsigned char *)(tcpclient->recvbuff.buf + proceed_len); unsigned short request_data_len; memcpy(&request_data_len, request_data, sizeof(unsigned short)); request_len = ntohs(request_data_len); if (request_len >= sizeof(tcpclient->recvbuff.buf)) { tlog(TLOG_DEBUG, "request length is invalid. len = %d", request_len); goto errout; } if (request_len > (total_len - proceed_len - sizeof(unsigned short))) { ret = RECV_ERROR_AGAIN; goto out; } request_data = (unsigned char *)(tcpclient->recvbuff.buf + proceed_len + sizeof(unsigned short)); proceed_len += sizeof(unsigned short) + request_len; } /* process one record */ ret = _dns_server_recv(&tcpclient->head, request_data, request_len, &tcpclient->localaddr, tcpclient->localaddr_len, &tcpclient->addr, tcpclient->addr_len); if (ret != 0) { goto errout; } if (http_head != NULL) { http_head_destroy(http_head); http_head = NULL; } } out: if (total_len > proceed_len && proceed_len > 0) { memmove(tcpclient->recvbuff.buf, tcpclient->recvbuff.buf + proceed_len, total_len - proceed_len); } tcpclient->recvbuff.size -= proceed_len; errout: if (http_head) { http_head_destroy(http_head); } if (http_decode_data) { free(http_decode_data); } if (base64_query) { free(base64_query); } if (tcpclient->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) { if (ret == RECV_ERROR_BAD_PATH) { _dns_server_reply_http_error(tcpclient, 404, "Not Found", "Not Found"); } else if (ret == RECV_ERROR_FAIL || ret == RECV_ERROR_INVALID_PACKET) { _dns_server_reply_http_error(tcpclient, 400, "Bad Request", "Bad Request"); } } return ret; } int _dns_server_tcp_process_requests(struct dns_server_conn_tcp_client *tcpclient) { int recv_ret = 0; int request_ret = 0; int is_eof = 0; int i = 0; for (i = 0; i < 32; i++) { recv_ret = _dns_server_tcp_recv(tcpclient); if (recv_ret < 0) { if (recv_ret == RECV_ERROR_CLOSE) { return RECV_ERROR_CLOSE; } if (tcpclient->recvbuff.size > 0) { is_eof = RECV_ERROR_AGAIN; } else { return RECV_ERROR_FAIL; } } request_ret = _dns_server_tcp_process_one_request(tcpclient); if (request_ret < 0) { /* failed */ tlog(TLOG_DEBUG, "process one request failed."); return RECV_ERROR_FAIL; } if (request_ret == RECV_ERROR_AGAIN && is_eof == RECV_ERROR_AGAIN) { /* failed or remote shutdown */ return RECV_ERROR_FAIL; } if (recv_ret == RECV_ERROR_AGAIN && request_ret == RECV_ERROR_AGAIN) { /* process complete */ return 0; } } return 0; } static int _dns_server_tls_want_write(struct dns_server_conn_tcp_client *tcpclient) { if (tcpclient->head.type == DNS_CONN_TYPE_TLS_CLIENT || tcpclient->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) { struct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)tcpclient; if (tls_client->ssl_want_write == 1) { return 1; } } return 0; } static int _dns_server_tcp_send(struct dns_server_conn_tcp_client *tcpclient) { int len = 0; while (tcpclient->sndbuff.size > 0 || _dns_server_tls_want_write(tcpclient) == 1) { len = _dns_server_tcp_socket_send(tcpclient, tcpclient->sndbuff.buf, tcpclient->sndbuff.size); if (len < 0) { if (errno == EAGAIN) { return RECV_ERROR_AGAIN; } return RECV_ERROR_FAIL; } else if (len == 0) { break; } tcpclient->sndbuff.size -= len; } if (_dns_server_epoll_ctl(&tcpclient->head, EPOLL_CTL_MOD, EPOLLIN) != 0) { tlog(TLOG_ERROR, "epoll ctl failed."); return -1; } return 0; } int _dns_server_process_tcp(struct dns_server_conn_tcp_client *dnsserver, struct epoll_event *event, unsigned long now) { int ret = 0; if (event->events & EPOLLIN) { ret = _dns_server_tcp_process_requests(dnsserver); if (ret != 0) { _dns_server_client_close(&dnsserver->head); if (ret == RECV_ERROR_CLOSE) { return 0; } tlog(TLOG_DEBUG, "process tcp request failed."); return RECV_ERROR_FAIL; } } if (event->events & EPOLLOUT) { if (_dns_server_tcp_send(dnsserver) != 0) { _dns_server_client_close(&dnsserver->head); tlog(TLOG_DEBUG, "send tcp failed."); return RECV_ERROR_FAIL; } } return 0; } void _dns_server_tcp_idle_check(void) { struct dns_server_conn_head *conn = NULL; struct dns_server_conn_head *tmp = NULL; time_t now = 0; time(&now); pthread_mutex_lock(&server.conn_list_lock); list_for_each_entry_safe(conn, tmp, &server.conn_list, list) { if (conn->type != DNS_CONN_TYPE_TCP_CLIENT && conn->type != DNS_CONN_TYPE_TLS_CLIENT && conn->type != DNS_CONN_TYPE_HTTPS_CLIENT) { continue; } struct dns_server_conn_tcp_client *tcpclient = (struct dns_server_conn_tcp_client *)conn; if (tcpclient->conn_idle_timeout <= 0) { continue; } if (conn->last_request_time > now - tcpclient->conn_idle_timeout) { continue; } _dns_server_client_close(conn); } pthread_mutex_unlock(&server.conn_list_lock); } int _dns_server_socket_tcp(struct dns_bind_ip *bind_ip) { const char *host_ip = NULL; struct dns_server_conn_tcp_server *conn = NULL; int fd = -1; const int on = 1; host_ip = bind_ip->ip; fd = _dns_create_socket(host_ip, SOCK_STREAM); if (fd <= 0) { goto errout; } setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &on, sizeof(on)); conn = zalloc(1, sizeof(struct dns_server_conn_tcp_server)); if (conn == NULL) { goto errout; } _dns_server_conn_head_init(&conn->head, fd, DNS_CONN_TYPE_TCP_SERVER); _dns_server_set_flags(&conn->head, bind_ip); _dns_server_conn_get(&conn->head); return 0; errout: if (conn) { free(conn); conn = NULL; } if (fd > 0) { close(fd); } return -1; } ================================================ FILE: src/dns_server/server_tcp.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_TCP_ #define _DNS_SERVER_TCP_ #include "dns_server.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_reply_tcp_to_buffer(struct dns_server_conn_tcp_client *tcpclient, void *packet, int len); int _dns_server_tcp_socket_send(struct dns_server_conn_tcp_client *tcp_client, void *data, int data_len); int _dns_server_tcp_accept(struct dns_server_conn_tcp_server *tcpserver, struct epoll_event *event, unsigned long now); int _dns_server_tcp_socket_recv(struct dns_server_conn_tcp_client *tcp_client, void *data, int data_len); int _dns_server_tcp_process_requests(struct dns_server_conn_tcp_client *tcpclient); int _dns_server_process_tcp(struct dns_server_conn_tcp_client *dnsserver, struct epoll_event *event, unsigned long now); int _dns_server_reply_tcp(struct dns_request *request, struct dns_server_conn_tcp_client *tcpclient, void *packet, unsigned short len); void _dns_server_tcp_idle_check(void); int _dns_server_socket_tcp(struct dns_bind_ip *bind_ip); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/server_tls.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "server_tls.h" #include "connection.h" #include "dns_server.h" #include "server_https.h" #include "server_socket.h" #include "server_tcp.h" #include "server_http2.h" #include "smartdns/http2.h" #include #include #include #include #include #include #include static int alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { struct dns_bind_ip *bind_ip = (struct dns_bind_ip *)arg; const char *alpn = bind_ip->alpn; if (alpn[0] == '\0') { alpn = "h2,http/1.1"; } /* Parse server ALPN list */ char alpn_copy[256]; safe_strncpy(alpn_copy, alpn, sizeof(alpn_copy)); char *saveptr = NULL; char *proto = strtok_r(alpn_copy, ",", &saveptr); while (proto) { unsigned int proto_len = strlen(proto); for (unsigned int i = 0; i < inlen;) { unsigned int len = in[i++]; if (i + len > inlen) { break; } if (len == proto_len && memcmp(&in[i], proto, len) == 0) { *out = &in[i]; *outlen = len; return SSL_TLSEXT_ERR_OK; } i += len; } proto = strtok_r(NULL, ",", &saveptr); } /* No match found */ tlog(TLOG_DEBUG, "ALPN negotiation failed: no matching protocol found"); return SSL_TLSEXT_ERR_NOACK; } static ssize_t _ssl_read(struct dns_server_conn_tls_client *conn, void *buff, int num) { ssize_t ret = 0; pthread_mutex_lock(&conn->ssl_lock); if (conn == NULL || buff == NULL) { pthread_mutex_unlock(&conn->ssl_lock); return SSL_ERROR_SYSCALL; } ret = SSL_read(conn->ssl, buff, num); pthread_mutex_unlock(&conn->ssl_lock); return ret; } static ssize_t _ssl_write(struct dns_server_conn_tls_client *conn, const void *buff, int num) { ssize_t ret = 0; pthread_mutex_lock(&conn->ssl_lock); if (conn == NULL || buff == NULL || conn->ssl == NULL) { pthread_mutex_unlock(&conn->ssl_lock); return SSL_ERROR_SYSCALL; } ret = SSL_write(conn->ssl, buff, num); pthread_mutex_unlock(&conn->ssl_lock); return ret; } static int _ssl_get_error(struct dns_server_conn_tls_client *conn, int ret) { int err = 0; pthread_mutex_lock(&conn->ssl_lock); if (conn == NULL || conn->ssl == NULL) { pthread_mutex_unlock(&conn->ssl_lock); return SSL_ERROR_SYSCALL; } err = SSL_get_error(conn->ssl, ret); pthread_mutex_unlock(&conn->ssl_lock); return err; } static int _ssl_do_accept(struct dns_server_conn_tls_client *conn) { int err = 0; pthread_mutex_lock(&conn->ssl_lock); if (conn == NULL || conn->ssl == NULL) { pthread_mutex_unlock(&conn->ssl_lock); return SSL_ERROR_SYSCALL; } err = SSL_accept(conn->ssl); pthread_mutex_unlock(&conn->ssl_lock); return err; } int _dns_server_socket_ssl_send(struct dns_server_conn_tls_client *tls_client, const void *buf, int num) { int ret = 0; int ssl_ret = 0; unsigned long ssl_err = 0; if (tls_client->ssl == NULL) { errno = EINVAL; return -1; } if (num < 0) { errno = EINVAL; return -1; } ret = _ssl_write(tls_client, buf, num); if (ret > 0) { return ret; } ssl_ret = _ssl_get_error(tls_client, ret); switch (ssl_ret) { case SSL_ERROR_NONE: case SSL_ERROR_ZERO_RETURN: return 0; break; case SSL_ERROR_WANT_READ: errno = EAGAIN; ret = -SSL_ERROR_WANT_READ; break; case SSL_ERROR_WANT_WRITE: errno = EAGAIN; ret = -SSL_ERROR_WANT_WRITE; break; case SSL_ERROR_SSL: ssl_err = ERR_get_error(); int ssl_reason = ERR_GET_REASON(ssl_err); if (ssl_reason == SSL_R_UNINITIALIZED || ssl_reason == SSL_R_PROTOCOL_IS_SHUTDOWN || ssl_reason == SSL_R_BAD_LENGTH || ssl_reason == SSL_R_SHUTDOWN_WHILE_IN_INIT || ssl_reason == SSL_R_BAD_WRITE_RETRY) { errno = EAGAIN; return -1; } tlog(TLOG_ERROR, "SSL write fail error no: %s(%d)\n", ERR_reason_error_string(ssl_err), ssl_reason); errno = EFAULT; ret = -1; break; case SSL_ERROR_SYSCALL: if (errno == 0) { tlog(TLOG_DEBUG, "SSL connection closed"); } else { tlog(TLOG_DEBUG, "SSL syscall failed, %s", strerror(errno)); } return ret; default: errno = EFAULT; ret = -1; break; } return ret; } int _dns_server_socket_ssl_recv(struct dns_server_conn_tls_client *tls_client, void *buf, int num) { ssize_t ret = 0; int ssl_ret = 0; unsigned long ssl_err = 0; if (tls_client->ssl == NULL) { errno = EFAULT; return -1; } ret = _ssl_read(tls_client, buf, num); if (ret > 0) { return ret; } ssl_ret = _ssl_get_error(tls_client, ret); switch (ssl_ret) { case SSL_ERROR_NONE: case SSL_ERROR_ZERO_RETURN: return 0; break; case SSL_ERROR_WANT_READ: errno = EAGAIN; ret = -SSL_ERROR_WANT_READ; break; case SSL_ERROR_WANT_WRITE: errno = EAGAIN; ret = -SSL_ERROR_WANT_WRITE; break; case SSL_ERROR_SSL: ssl_err = ERR_get_error(); int ssl_reason = ERR_GET_REASON(ssl_err); if (ssl_reason == SSL_R_UNINITIALIZED) { errno = EAGAIN; return -1; } if (ssl_reason == SSL_R_SHUTDOWN_WHILE_IN_INIT || ssl_reason == SSL_R_PROTOCOL_IS_SHUTDOWN) { return 0; } #ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING if (ssl_reason == SSL_R_UNEXPECTED_EOF_WHILE_READING) { return 0; } #endif tlog(TLOG_DEBUG, "SSL read fail error no: %s(%lx), reason: %d\n", ERR_reason_error_string(ssl_err), ssl_err, ssl_reason); errno = EFAULT; ret = -1; break; case SSL_ERROR_SYSCALL: if (errno == 0) { return 0; } ret = -1; return ret; default: errno = EFAULT; ret = -1; break; } return ret; } int _dns_server_ssl_poll_event(struct dns_server_conn_tls_client *tls_client, int ssl_ret) { struct epoll_event fd_event; memset(&fd_event, 0, sizeof(fd_event)); if (ssl_ret == SSL_ERROR_WANT_READ) { fd_event.events = EPOLLIN; } else if (ssl_ret == SSL_ERROR_WANT_WRITE) { fd_event.events = EPOLLOUT | EPOLLIN; } else { goto errout; } fd_event.data.ptr = tls_client; if (epoll_ctl(server.epoll_fd, EPOLL_CTL_MOD, tls_client->tcp.head.fd, &fd_event) != 0) { if (errno == ENOENT) { /* fd not found, ignore */ return 0; } tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); goto errout; } return 0; errout: return -1; } int _dns_server_tls_accept(struct dns_server_conn_tls_server *tls_server, struct epoll_event *event, unsigned long now) { struct sockaddr_storage addr; struct dns_server_conn_tls_client *tls_client = NULL; DNS_CONN_TYPE conn_type; socklen_t addr_len = sizeof(addr); int fd = -1; SSL *ssl = NULL; fd = accept4(tls_server->head.fd, (struct sockaddr *)&addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC); if (fd < 0) { tlog(TLOG_ERROR, "accept failed, %s", strerror(errno)); return -1; } if (tls_server->head.type == DNS_CONN_TYPE_TLS_SERVER) { conn_type = DNS_CONN_TYPE_TLS_CLIENT; } else if (tls_server->head.type == DNS_CONN_TYPE_HTTPS_SERVER) { conn_type = DNS_CONN_TYPE_HTTPS_CLIENT; } else { tlog(TLOG_ERROR, "invalid http server type."); goto errout; } tls_client = zalloc(1, sizeof(*tls_client)); if (tls_client == NULL) { tlog(TLOG_ERROR, "malloc for tls_client failed."); goto errout; } _dns_server_conn_head_init(&tls_client->tcp.head, fd, conn_type); tls_client->tcp.head.server_flags = tls_server->head.server_flags; tls_client->tcp.head.dns_group = tls_server->head.dns_group; tls_client->tcp.head.ipset_nftset_rule = tls_server->head.ipset_nftset_rule; tls_client->tcp.conn_idle_timeout = dns_conf.tcp_idle_time; atomic_set(&tls_client->tcp.head.refcnt, 0); memcpy(&tls_client->tcp.addr, &addr, addr_len); tls_client->tcp.addr_len = addr_len; tls_client->tcp.localaddr_len = sizeof(struct sockaddr_storage); if (_dns_server_epoll_ctl(&tls_client->tcp.head, EPOLL_CTL_ADD, EPOLLIN) != 0) { tlog(TLOG_ERROR, "epoll ctl failed."); return -1; } if (getsocket_inet(tls_client->tcp.head.fd, (struct sockaddr *)&tls_client->tcp.localaddr, &tls_client->tcp.localaddr_len) != 0) { tlog(TLOG_ERROR, "get local addr failed, %s", strerror(errno)); goto errout; } ssl = SSL_new(tls_server->ssl_ctx); if (ssl == NULL) { tlog(TLOG_ERROR, "SSL_new failed."); goto errout; } if (SSL_set_fd(ssl, fd) != 1) { tlog(TLOG_ERROR, "SSL_set_fd failed."); goto errout; } tls_client->ssl = ssl; tls_client->tcp.status = DNS_SERVER_CLIENT_STATUS_CONNECTING; pthread_mutex_init(&tls_client->ssl_lock, NULL); _dns_server_client_touch(&tls_client->tcp.head); pthread_mutex_lock(&server.conn_list_lock); list_add(&tls_client->tcp.head.list, &server.conn_list); pthread_mutex_unlock(&server.conn_list_lock); _dns_server_conn_get(&tls_client->tcp.head); set_sock_keepalive(fd, 30, 3, 5); return 0; errout: if (fd > 0) { close(fd); } if (ssl) { SSL_free(ssl); } if (tls_client) { free(tls_client); } return -1; } int _dns_server_process_tls(struct dns_server_conn_tls_client *tls_client, struct epoll_event *event, unsigned long now) { int ret = 0; int ssl_ret = 0; struct epoll_event fd_event; if (tls_client->tcp.status == DNS_SERVER_CLIENT_STATUS_CONNECTING) { /* do SSL hand shake */ ret = _ssl_do_accept(tls_client); if (ret <= 0) { memset(&fd_event, 0, sizeof(fd_event)); ssl_ret = _ssl_get_error(tls_client, ret); if (_dns_server_ssl_poll_event(tls_client, ssl_ret) == 0) { return 0; } if (ssl_ret != SSL_ERROR_SYSCALL) { unsigned long ssl_err = ERR_get_error(); int ssl_reason = ERR_GET_REASON(ssl_err); char name[DNS_MAX_CNAME_LEN]; tlog(TLOG_DEBUG, "Handshake with %s failed, error no: %s(%d, %d, %d)\n", get_host_by_addr(name, sizeof(name), (struct sockaddr *)&tls_client->tcp.addr), ERR_reason_error_string(ssl_err), ret, ssl_ret, ssl_reason); ret = 0; } goto errout; } tls_client->tcp.status = DNS_SERVER_CLIENT_STATUS_CONNECTED; /* Get negotiated ALPN */ const unsigned char *alpn_data = NULL; unsigned int alpn_len = 0; SSL_get0_alpn_selected(tls_client->ssl, &alpn_data, &alpn_len); if (alpn_data && alpn_len > 0 && alpn_len < sizeof(tls_client->alpn_selected)) { memcpy(tls_client->alpn_selected, alpn_data, alpn_len); tls_client->alpn_selected[alpn_len] = '\0'; } else { safe_strncpy(tls_client->alpn_selected, "http/1.1", sizeof(tls_client->alpn_selected)); } memset(&fd_event, 0, sizeof(fd_event)); fd_event.events = EPOLLIN | EPOLLOUT; fd_event.data.ptr = tls_client; if (epoll_ctl(server.epoll_fd, EPOLL_CTL_MOD, tls_client->tcp.head.fd, &fd_event) != 0) { tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); goto errout; } } /* if HTTP/2 was negotiated */ if (strcmp(tls_client->alpn_selected, "h2") == 0) { ret = _dns_server_process_http2(tls_client, event, now); if (ret != 0) { goto errout; } return ret; } return _dns_server_process_tcp((struct dns_server_conn_tcp_client *)tls_client, event, now); errout: _dns_server_client_close(&tls_client->tcp.head); return ret; } static int _dns_server_socket_tls_ssl_pass_callback(char *buf, int size, int rwflag, void *userdata) { struct dns_bind_ip *bind_ip = userdata; if (bind_ip->ssl_cert_key_pass == NULL || bind_ip->ssl_cert_key_pass[0] == '\0') { return 0; } safe_strncpy(buf, bind_ip->ssl_cert_key_pass, size); return strlen(buf); } int _dns_server_socket_tls(struct dns_bind_ip *bind_ip, DNS_CONN_TYPE conn_type) { const char *host_ip = NULL; const char *ssl_cert_file = NULL; const char *ssl_cert_key_file = NULL; struct dns_server_conn_tls_server *conn = NULL; int fd = -1; const SSL_METHOD *method = NULL; SSL_CTX *ssl_ctx = NULL; const int on = 1; host_ip = bind_ip->ip; ssl_cert_file = bind_ip->ssl_cert_file; ssl_cert_key_file = bind_ip->ssl_cert_key_file; if (ssl_cert_file == NULL || ssl_cert_key_file == NULL) { tlog(TLOG_WARN, "no cert or cert key file"); goto errout; } if (ssl_cert_file[0] == '\0' || ssl_cert_key_file[0] == '\0') { tlog(TLOG_WARN, "no cert or cert key file"); goto errout; } fd = _dns_create_socket(host_ip, SOCK_STREAM); if (fd <= 0) { goto errout; } setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &on, sizeof(on)); #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) method = TLS_server_method(); if (method == NULL) { goto errout; } #else method = SSLv23_server_method(); #endif ssl_ctx = SSL_CTX_new(method); if (ssl_ctx == NULL) { goto errout; } SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR); SSL_CTX_set_default_passwd_cb(ssl_ctx, _dns_server_socket_tls_ssl_pass_callback); SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, bind_ip); /* Set the key and cert */ if (ssl_cert_file[0] != '\0' && SSL_CTX_use_certificate_chain_file(ssl_ctx, ssl_cert_file) <= 0) { tlog(TLOG_ERROR, "load cert %s failed, %s", ssl_cert_file, ERR_error_string(ERR_get_error(), NULL)); goto errout; } if (ssl_cert_key_file[0] != '\0' && SSL_CTX_use_PrivateKey_file(ssl_ctx, ssl_cert_key_file, SSL_FILETYPE_PEM) <= 0) { tlog(TLOG_ERROR, "load cert key %s failed, %s", ssl_cert_key_file, ERR_error_string(ERR_get_error(), NULL)); goto errout; } /* Set ALPN */ SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_cb, bind_ip); conn = zalloc(1, sizeof(struct dns_server_conn_tls_server)); if (conn == NULL) { goto errout; } _dns_server_conn_head_init(&conn->head, fd, conn_type); conn->ssl_ctx = ssl_ctx; _dns_server_set_flags(&conn->head, bind_ip); _dns_server_conn_get(&conn->head); return 0; errout: if (ssl_ctx) { SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; } if (conn) { free(conn); conn = NULL; } if (fd > 0) { close(fd); } return -1; } ================================================ FILE: src/dns_server/server_tls.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_TLS_ #define _DNS_SERVER_TLS_ #include "dns_server.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_ssl_poll_event(struct dns_server_conn_tls_client *tls_client, int ssl_ret); int _dns_server_tls_accept(struct dns_server_conn_tls_server *tls_server, struct epoll_event *event, unsigned long now); int _dns_server_socket_ssl_recv(struct dns_server_conn_tls_client *tls_client, void *buf, int num); int _dns_server_socket_ssl_send(struct dns_server_conn_tls_client *tls_client, const void *buf, int num); int _dns_server_process_tls(struct dns_server_conn_tls_client *tls_client, struct epoll_event *event, unsigned long now); int _dns_server_socket_tls(struct dns_bind_ip *bind_ip, DNS_CONN_TYPE conn_type); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/server_udp.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "server_udp.h" #include "connection.h" #include "dns_server.h" #include "server_socket.h" #include #include #include #include #include #include int _dns_server_reply_udp(struct dns_request *request, struct dns_server_conn_udp *udpserver, unsigned char *inpacket, int inpacket_len) { int send_len = 0; struct iovec iovec[1]; struct msghdr msg; struct cmsghdr *cmsg; char msg_control[64]; if (atomic_read(&server.run) == 0 || inpacket == NULL || inpacket_len <= 0) { return -1; } iovec[0].iov_base = inpacket; iovec[0].iov_len = inpacket_len; memset(msg_control, 0, sizeof(msg_control)); msg.msg_iov = iovec; msg.msg_iovlen = 1; msg.msg_control = msg_control; msg.msg_controllen = sizeof(msg_control); msg.msg_flags = 0; msg.msg_name = &request->addr; msg.msg_namelen = request->addr_len; cmsg = CMSG_FIRSTHDR(&msg); if (request->localaddr.ss_family == AF_INET) { struct sockaddr_in *s4 = (struct sockaddr_in *)&request->localaddr; cmsg->cmsg_level = SOL_IP; cmsg->cmsg_type = IP_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); memset(pktinfo, 0, sizeof(*pktinfo)); pktinfo->ipi_spec_dst = s4->sin_addr; } else if (request->localaddr.ss_family == AF_INET6) { struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)&request->localaddr; cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); memset(pktinfo, 0, sizeof(*pktinfo)); pktinfo->ipi6_addr = s6->sin6_addr; } else { goto use_send; } send_len = sendmsg(udpserver->head.fd, &msg, 0); if (send_len == inpacket_len) { return 0; } use_send: send_len = sendto(udpserver->head.fd, inpacket, inpacket_len, 0, &request->addr, request->addr_len); if (send_len != inpacket_len) { tlog(TLOG_DEBUG, "send failed, %s", strerror(errno)); return -1; } return 0; } static int _dns_server_process_udp_one(struct dns_server_conn_udp *udpconn, struct epoll_event *event, unsigned long now) { int len = 0; unsigned char inpacket[DNS_IN_PACKSIZE]; struct sockaddr_storage from; socklen_t from_len = sizeof(from); struct sockaddr_storage local; socklen_t local_len = sizeof(local); struct msghdr msg; struct iovec iov; char ans_data[4096]; struct cmsghdr *cmsg = NULL; memset(&msg, 0, sizeof(msg)); iov.iov_base = (char *)inpacket; iov.iov_len = sizeof(inpacket); msg.msg_name = &from; msg.msg_namelen = sizeof(from); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = ans_data; msg.msg_controllen = sizeof(ans_data); len = recvmsg(udpconn->head.fd, &msg, MSG_DONTWAIT); if (len < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return -2; } tlog(TLOG_ERROR, "recvfrom failed, %s\n", strerror(errno)); return -1; } from_len = msg.msg_namelen; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { const struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); unsigned char *addr = (unsigned char *)&pktinfo->ipi_addr.s_addr; fill_sockaddr_by_ip(addr, sizeof(in_addr_t), 0, (struct sockaddr *)&local, &local_len); } else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { const struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); unsigned char *addr = (unsigned char *)pktinfo->ipi6_addr.s6_addr; fill_sockaddr_by_ip(addr, sizeof(struct in6_addr), 0, (struct sockaddr *)&local, &local_len); } } return _dns_server_recv(&udpconn->head, inpacket, len, &local, local_len, &from, from_len); } int _dns_server_process_udp(struct dns_server_conn_udp *udpconn, struct epoll_event *event, unsigned long now) { int count = 0; while (count < 32) { int ret = _dns_server_process_udp_one(udpconn, event, now); if (ret != 0) { if (ret == -2) { return 0; } return ret; } count++; } return 0; } int _dns_server_socket_udp(struct dns_bind_ip *bind_ip) { const char *host_ip = NULL; struct dns_server_conn_udp *conn = NULL; int fd = -1; host_ip = bind_ip->ip; fd = _dns_create_socket(host_ip, SOCK_DGRAM); if (fd <= 0) { goto errout; } conn = zalloc(1, sizeof(struct dns_server_conn_udp)); if (conn == NULL) { goto errout; } _dns_server_conn_head_init(&conn->head, fd, DNS_CONN_TYPE_UDP_SERVER); _dns_server_set_flags(&conn->head, bind_ip); _dns_server_conn_get(&conn->head); return 0; errout: if (conn) { free(conn); conn = NULL; } if (fd > 0) { close(fd); } return -1; } ================================================ FILE: src/dns_server/server_udp.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_UDP_ #define _DNS_SERVER_UDP_ #include "dns_server.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_process_udp(struct dns_server_conn_udp *udpconn, struct epoll_event *event, unsigned long now); int _dns_server_socket_udp(struct dns_bind_ip *bind_ip); int _dns_server_reply_udp(struct dns_request *request, struct dns_server_conn_udp *udpserver, unsigned char *inpacket, int inpacket_len); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/soa.c ================================================ /************************************************************************* * * 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 . */ #include "soa.h" #include "context.h" #include "dns_server.h" #include "request.h" #include "rules.h" #include "smartdns/dns_stats.h" int _dns_server_is_return_soa_qtype(struct dns_request *request, dns_type_t qtype) { uint32_t flags = _dns_server_get_rule_flags(request); if (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_SOA) == 0) { /* when both has no rule SOA and force AAAA soa, force AAAA soa has high priority */ if (qtype == DNS_T_AAAA && _dns_server_has_bind_flag(request, BIND_FLAG_FORCE_AAAA_SOA) == 0) { return 1; } return 0; } if (flags & DOMAIN_FLAG_ADDR_IGN) { request->skip_qtype_soa = 1; return 0; } if (flags & DOMAIN_FLAG_ADDR_SOA) { stats_inc(&dns_stats.request.blocked_count); return 1; } switch (qtype) { case DNS_T_A: if (flags & DOMAIN_FLAG_ADDR_IPV4_SOA) { stats_inc(&dns_stats.request.blocked_count); return 1; } if (flags & DOMAIN_FLAG_ADDR_IPV4_IGN) { request->skip_qtype_soa = 1; return 0; } break; case DNS_T_AAAA: if (flags & DOMAIN_FLAG_ADDR_IPV6_SOA) { stats_inc(&dns_stats.request.blocked_count); return 1; } if (flags & DOMAIN_FLAG_ADDR_IPV6_IGN) { request->skip_qtype_soa = 1; return 0; } break; case DNS_T_HTTPS: if (flags & DOMAIN_FLAG_ADDR_HTTPS_SOA) { stats_inc(&dns_stats.request.blocked_count); return 1; } if (flags & DOMAIN_FLAG_ADDR_HTTPS_IGN) { request->skip_qtype_soa = 1; return 0; } break; default: break; } if (qtype == DNS_T_AAAA) { if (_dns_server_has_bind_flag(request, BIND_FLAG_FORCE_AAAA_SOA) == 0 || request->conf->force_AAAA_SOA == 1) { return 1; } if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] != NULL && request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] == NULL) { return 1; } } else if (qtype == DNS_T_A) { if (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] != NULL && request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] == NULL) { return 1; } } return 0; } int _dns_server_reply_SOA_ext(int rcode, struct dns_request *request) { /* return SOA record */ request->rcode = rcode; if (request->ip_ttl <= 0) { request->ip_ttl = DNS_SERVER_SOA_TTL; } request->has_soa = 1; struct dns_server_post_context context; _dns_server_post_context_init(&context, request); context.do_audit = 1; context.do_reply = 1; context.do_force_soa = 1; _dns_request_post(&context); return 0; } int _dns_server_reply_SOA(int rcode, struct dns_request *request) { _dns_server_setup_soa(request); return _dns_server_reply_SOA_ext(rcode, request); } int _dns_server_qtype_soa(struct dns_request *request) { if (request->skip_qtype_soa || request->conf->soa_table == NULL) { return -1; } if (request->qtype >= 0 && request->qtype <= MAX_QTYPE_NUM) { int offset = request->qtype / 8; int bit = request->qtype % 8; if ((request->conf->soa_table[offset] & (1 << bit)) == 0) { return -1; } } _dns_server_reply_SOA(DNS_RC_NOERROR, request); tlog(TLOG_DEBUG, "force qtype %d soa", request->qtype); return 0; } int _dns_server_is_return_soa(struct dns_request *request) { return _dns_server_is_return_soa_qtype(request, request->qtype); } void _dns_server_setup_soa(struct dns_request *request) { struct dns_soa *soa = NULL; soa = &request->soa; safe_strncpy(soa->mname, "a.gtld-servers.net", DNS_MAX_CNAME_LEN); safe_strncpy(soa->rname, "nstld.verisign-grs.com", DNS_MAX_CNAME_LEN); soa->serial = 1800; soa->refresh = 1800; soa->retry = 900; soa->expire = 604800; soa->minimum = 86400; } ================================================ FILE: src/dns_server/soa.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_SOA_ #define _DNS_SERVER_SOA_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_is_return_soa(struct dns_request *request); void _dns_server_setup_soa(struct dns_request *request); int _dns_server_is_return_soa_qtype(struct dns_request *request, dns_type_t qtype); int _dns_server_reply_SOA(int rcode, struct dns_request *request); int _dns_server_reply_SOA_ext(int rcode, struct dns_request *request); int _dns_server_qtype_soa(struct dns_request *request); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_server/speed_check.c ================================================ /************************************************************************* * * 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 . */ #include "speed_check.h" #include "address.h" #include "dns_server.h" #include "dualstack.h" #include "request.h" #include "smartdns/fast_ping.h" #include #include static void _dns_server_ping_result(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result, struct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv, int error, void *userptr) { struct dns_request *request = userptr; int may_complete = 0; int threshold = 100; struct dns_ip_address *addr_map = NULL; int last_rtt = 0; if (request == NULL) { return; } last_rtt = request->ping_time; if (result == PING_RESULT_END) { _dns_server_request_release(request); fast_ping_stop(ping_host); return; } else if (result == PING_RESULT_TIMEOUT) { tlog(TLOG_DEBUG, "ping %s timeout", host); goto out; return; } else if (result == PING_RESULT_ERROR) { if (addr->sa_family != AF_INET6) { return; } if (is_ipv6_ready == 1 && (error == EADDRNOTAVAIL || errno == EACCES)) { if (is_private_addr_sockaddr(addr, addr_len) == 0) { is_ipv6_ready = 0; tlog(TLOG_WARN, "IPV6 is not ready, disable all ipv6 feature, recheck after %ds", IPV6_READY_CHECK_TIME); } } return; } int rtt = tv->tv_sec * 10000 + tv->tv_usec / 100; if (rtt == 0) { rtt = 1; } if (result == PING_RESULT_RESPONSE) { tlog(TLOG_DEBUG, "from %s: seq=%d time=%d, lasttime=%d id=%d", host, seqno, rtt, last_rtt, request->id); } else { tlog(TLOG_DEBUG, "from %s: seq=%d timeout, id=%d", host, seqno, request->id); } switch (addr->sa_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)addr; addr_map = _dns_ip_address_get(request, (unsigned char *)&addr_in->sin_addr.s_addr, DNS_T_A); if (addr_map) { addr_map->ping_time = rtt; } if (request->ping_time > rtt || request->ping_time == -1) { memcpy(request->ip_addr, &addr_in->sin_addr.s_addr, 4); request->ip_addr_type = DNS_T_A; request->ping_time = rtt; request->has_cname = 0; request->has_ip = 1; if (addr_map && addr_map->cname[0] != 0) { request->has_cname = 1; safe_strncpy(request->cname, addr_map->cname, DNS_MAX_CNAME_LEN); } else { request->has_cname = 0; } } if (request->qtype == DNS_T_AAAA && request->dualstack_selection) { if (request->ping_time < 0 && request->has_soa == 0) { return; } } if (request->qtype == DNS_T_A || request->qtype == DNS_T_HTTPS) { request->has_ping_result = 1; } } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { addr_map = _dns_ip_address_get(request, addr_in6->sin6_addr.s6_addr + 12, DNS_T_A); if (addr_map) { addr_map->ping_time = rtt; } if (request->ping_time > rtt || request->ping_time == -1) { request->ping_time = rtt; request->has_cname = 0; request->has_ip = 1; memcpy(request->ip_addr, addr_in6->sin6_addr.s6_addr, 16); request->ip_addr_type = DNS_T_A; if (addr_map && addr_map->cname[0] != 0) { request->has_cname = 1; safe_strncpy(request->cname, addr_map->cname, DNS_MAX_CNAME_LEN); } else { request->has_cname = 0; } } if (request->qtype == DNS_T_A || request->qtype == DNS_T_HTTPS) { request->has_ping_result = 1; } } else { addr_map = _dns_ip_address_get(request, addr_in6->sin6_addr.s6_addr, DNS_T_AAAA); if (addr_map) { addr_map->ping_time = rtt; } if (request->ping_time > rtt || request->ping_time == -1) { request->ping_time = rtt; request->has_cname = 0; request->has_ip = 1; memcpy(request->ip_addr, addr_in6->sin6_addr.s6_addr, 16); request->ip_addr_type = DNS_T_AAAA; if (addr_map && addr_map->cname[0] != 0) { request->has_cname = 1; safe_strncpy(request->cname, addr_map->cname, DNS_MAX_CNAME_LEN); } else { request->has_cname = 0; } } if (request->qtype == DNS_T_AAAA || request->qtype == DNS_T_HTTPS) { request->has_ping_result = 1; } } } break; default: break; } out: /* If the ping delay is less than the threshold, the result is returned */ if (request->ping_time > 0) { if (request->ping_time < threshold) { may_complete = 1; } else if (request->ping_time < (int)(get_tick_count() - request->send_tick)) { may_complete = 1; } } /* Get first ping result */ if (request->response_mode == DNS_RESPONSE_MODE_FIRST_PING_IP && last_rtt == -1 && request->ping_time > 0) { may_complete = 1; } if (may_complete && request->has_ping_result == 1) { _dns_server_request_complete(request); } } static int _dns_server_ping(struct dns_request *request, PING_TYPE type, char *ip, int timeout) { if (fast_ping_start(type, ip, 1, 0, timeout, _dns_server_ping_result, request) == NULL) { return -1; } return 0; } int _dns_server_check_speed(struct dns_request *request, char *ip) { char tcp_ip[DNS_MAX_CNAME_LEN] = {0}; int port = 80; int type = DOMAIN_CHECK_NONE; int order = request->check_order; int ping_timeout = DNS_PING_TIMEOUT; unsigned long now = get_tick_count(); if (order >= DOMAIN_CHECK_NUM || request->check_order_list == NULL) { return -1; } if (request->passthrough) { return -1; } ping_timeout = ping_timeout - (now - request->send_tick); switch (request->response_mode) { case DNS_RESPONSE_MODE_FIRST_PING_IP: if (ping_timeout > 200) { ping_timeout = 200; } break; case DNS_RESPONSE_MODE_FASTEST_IP: if (ping_timeout > DNS_PING_TIMEOUT) { ping_timeout = DNS_PING_TIMEOUT; } else if (ping_timeout < 200) { ping_timeout = 200; } break; case DNS_RESPONSE_MODE_FASTEST_RESPONSE: if (ping_timeout < 200) { ping_timeout = 200; } break; default: break; } if (ping_timeout < 200) { ping_timeout = 200; } port = request->check_order_list->orders[order].tcp_port; type = request->check_order_list->orders[order].type; switch (type) { case DOMAIN_CHECK_ICMP: tlog(TLOG_DEBUG, "ping %s with icmp, order: %d, timeout: %d", ip, order, ping_timeout); return _dns_server_ping(request, PING_TYPE_ICMP, ip, ping_timeout); break; case DOMAIN_CHECK_TCP: snprintf(tcp_ip, sizeof(tcp_ip), "%s:%d", ip, port); tlog(TLOG_DEBUG, "ping %s with tcp, order: %d, timeout: %d", tcp_ip, order, ping_timeout); return _dns_server_ping(request, PING_TYPE_TCP, tcp_ip, ping_timeout); break; case DOMAIN_CHECK_TCP_SYN: snprintf(tcp_ip, sizeof(tcp_ip), "%s:%d", ip, port); tlog(TLOG_DEBUG, "ping %s with tcp-syn, order: %d, timeout: %d", tcp_ip, order, ping_timeout); return _dns_server_ping(request, PING_TYPE_TCP_SYN, tcp_ip, ping_timeout); break; default: break; } return -1; } int _dns_server_second_ping_check(struct dns_request *request) { struct dns_ip_address *addr_map = NULL; unsigned long bucket = 0; char ip[DNS_MAX_CNAME_LEN] = {0}; int ret = -1; if (request->has_ping_result) { return ret; } /* start tcping */ pthread_mutex_lock(&request->ip_map_lock); hash_for_each(request->ip_map, bucket, addr_map, node) { switch (addr_map->addr_type) { case DNS_T_A: { _dns_server_request_get(request); snprintf(ip, sizeof(ip), "%d.%d.%d.%d", addr_map->ip_addr[0], addr_map->ip_addr[1], addr_map->ip_addr[2], addr_map->ip_addr[3]); ret = _dns_server_check_speed(request, ip); if (ret != 0) { _dns_server_request_release(request); } } break; case DNS_T_AAAA: { _dns_server_request_get(request); snprintf(ip, sizeof(ip), "[%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x]", addr_map->ip_addr[0], addr_map->ip_addr[1], addr_map->ip_addr[2], addr_map->ip_addr[3], addr_map->ip_addr[4], addr_map->ip_addr[5], addr_map->ip_addr[6], addr_map->ip_addr[7], addr_map->ip_addr[8], addr_map->ip_addr[9], addr_map->ip_addr[10], addr_map->ip_addr[11], addr_map->ip_addr[12], addr_map->ip_addr[13], addr_map->ip_addr[14], addr_map->ip_addr[15]); ret = _dns_server_check_speed(request, ip); if (ret != 0) { _dns_server_request_release(request); } } break; default: break; } } pthread_mutex_unlock(&request->ip_map_lock); return ret; } ================================================ FILE: src/dns_server/speed_check.h ================================================ /************************************************************************* * * 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 . */ #ifndef _DNS_SERVER_SPEED_CHECK_ #define _DNS_SERVER_SPEED_CHECK_ #include "dns_server.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _dns_server_second_ping_check(struct dns_request *request); int _dns_server_check_speed(struct dns_request *request, char *ip); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/dns_stats.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/dns_stats.h" #include #include struct dns_stats dns_stats; void dns_stats_avg_time_update_add(struct dns_stats_avg_time *avg_time, uint64_t time) { if (avg_time == NULL) { return; } uint64_t total = (uint64_t)1 << 32 | time; return stats_add(&avg_time->total, total); } void dns_stats_avg_time_update(struct dns_stats_avg_time *avg_time) { uint64_t total = stats_read_and_set(&avg_time->total, 0); uint64_t count = total >> 32; uint64_t time = total & 0xFFFFFFFF; if (count == 0) { return; } double sample_avg = (double)time / count; if (avg_time->count == 0) { avg_time->avg_time = sample_avg; avg_time->count = count; return; } int base = 1000; if (count > 100) { count = 100; } double weight_new = (double)count / base; double weight_prev = 1.0 - weight_new; avg_time->avg_time = (avg_time->avg_time * weight_prev) + (sample_avg * weight_new); avg_time->count += count; } void dns_stats_period_run_second(void) { dns_stats_avg_time_update(&dns_stats.avg_time); } float dns_stats_avg_time_get(void) { return dns_stats.avg_time.avg_time; } uint64_t dns_stats_request_total_get(void) { return stats_read(&dns_stats.request.total); } uint64_t dns_stats_request_success_get(void) { return stats_read(&dns_stats.request.success_count); } uint64_t dns_stats_request_from_client_get(void) { return stats_read(&dns_stats.request.from_client_count); } uint64_t dns_stats_request_blocked_get(void) { return stats_read(&dns_stats.request.blocked_count); } uint64_t dns_stats_cache_hit_get(void) { return stats_read(&dns_stats.cache.hit_count); } float dns_stats_cache_hit_rate_get(void) { uint64_t total = stats_read(&dns_stats.cache.check_count); uint64_t hit = stats_read(&dns_stats.cache.hit_count); if (total == 0) { return 0; } return (float)(hit * 100) / total; } void dns_stats_server_stats_avg_time_add(struct dns_server_stats *server_stats, uint64_t time) { dns_stats_avg_time_update_add(&server_stats->avg_time, time); } void dns_stats_server_stats_avg_time_update(struct dns_server_stats *server_stats) { dns_stats_avg_time_update(&server_stats->avg_time); } uint64_t dns_stats_server_stats_total_get(struct dns_server_stats *server_stats) { if (server_stats == NULL) { return 0; } return stats_read(&server_stats->total); } uint64_t dns_stats_server_stats_success_get(struct dns_server_stats *server_stats) { if (server_stats == NULL) { return 0; } return stats_read(&server_stats->success_count); } uint64_t dns_stats_server_stats_recv_get(struct dns_server_stats *server_stats) { if (server_stats == NULL) { return 0; } return stats_read(&server_stats->recv_count); } float dns_stats_server_stats_avg_time_get(struct dns_server_stats *server_stats) { if (server_stats == NULL) { return 0; } return server_stats->avg_time.avg_time; } int dns_stats_init(void) { memset(&dns_stats, 0, sizeof(dns_stats)); return 0; } void dns_stats_exit(void) { memset(&dns_stats, 0, sizeof(dns_stats)); return; } ================================================ FILE: src/fast_ping/fast_ping.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/fast_ping.h" #include "smartdns/lib/atomic.h" #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include "fast_ping.h" #include "notify_event.h" #include "ping_fake.h" #include "ping_host.h" #include "ping_icmp.h" #include "ping_icmp6.h" #include "ping_tcp.h" #include "ping_tcp_syn.h" #include "ping_udp.h" #include "wakeup_event.h" #include #include #include #include #include #include #include #include #include #include static int is_fast_ping_init; struct fast_ping_struct ping; static atomic_t ping_sid = ATOMIC_INIT(0); int bool_print_log = 1; uint32_t _fast_ping_hash_key(unsigned int sid, struct sockaddr *addr) { uint32_t key = 0; void *sin_addr = NULL; unsigned int sin_addr_len = 0; switch (addr->sa_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)addr; sin_addr = &addr_in->sin_addr.s_addr; sin_addr_len = IPV4_ADDR_LEN; } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { sin_addr = addr_in6->sin6_addr.s6_addr + 12; sin_addr_len = IPV4_ADDR_LEN; } else { sin_addr = addr_in6->sin6_addr.s6_addr; sin_addr_len = IPV6_ADDR_LEN; } } break; default: goto errout; break; } if (sin_addr == NULL) { return -1; } key = jhash(sin_addr, sin_addr_len, 0); key = jhash(&sid, sizeof(sid), key); return key; errout: return -1; } struct addrinfo *_fast_ping_getaddr(const char *host, const char *port, int type, int protocol) { struct addrinfo hints; struct addrinfo *result = NULL; int errcode = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = type; hints.ai_protocol = protocol; errcode = getaddrinfo(host, port, &hints, &result); if (errcode != 0) { tlog(TLOG_ERROR, "get addr info failed. host:%s, port: %s, error %s\n", host != NULL ? host : "", port != NULL ? port : "", gai_strerror(errcode)); goto errout; } return result; errout: if (result) { freeaddrinfo(result); } return NULL; } int _fast_ping_getdomain(const char *host) { struct addrinfo hints; struct addrinfo *result = NULL; int domain = -1; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; if (getaddrinfo(host, NULL, &hints, &result) != 0) { tlog(TLOG_ERROR, "get addr info failed. %s\n", strerror(errno)); goto errout; } domain = result->ai_family; freeaddrinfo(result); return domain; errout: if (result) { freeaddrinfo(result); } return -1; } static int _fast_ping_sendping(struct ping_host_struct *ping_host) { int ret = -1; struct fast_ping_fake_ip *fake = NULL; gettimeofday(&ping_host->last, NULL); fake = _fast_ping_fake_find(ping_host->type, &ping_host->addr, ping_host->addr_len); if (fake) { ret = _fast_ping_send_fake(ping_host, fake); _fast_ping_fake_put(fake); return ret; } if (ping_host->type == FAST_PING_ICMP) { ret = _fast_ping_sendping_v4(ping_host); } else if (ping_host->type == FAST_PING_ICMP6) { ret = _fast_ping_sendping_v6(ping_host); } else if (ping_host->type == FAST_PING_TCP) { ret = _fast_ping_sendping_tcp(ping_host); } else if (ping_host->type == FAST_PING_TCP_SYN) { ret = _fast_ping_sendping_tcp_syn(ping_host); } else if (ping_host->type == FAST_PING_UDP || ping_host->type == FAST_PING_UDP6) { ret = _fast_ping_sendping_udp(ping_host); } ping_host->send = 1; if (ret != 0) { ping_host->error = errno; return ret; } else { ping_host->error = 0; } return 0; } static void _fast_ping_print_result(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result, struct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv, int error, void *userptr) { if (result == PING_RESULT_RESPONSE) { double rtt = tv->tv_sec * 1000.0 + tv->tv_usec / 1000.0; tlog(TLOG_INFO, "from %15s: seq=%d ttl=%d time=%.3f\n", host, seqno, ttl, rtt); } else if (result == PING_RESULT_TIMEOUT) { tlog(TLOG_INFO, "from %15s: seq=%d timeout\n", host, seqno); } else if (result == PING_RESULT_ERROR) { tlog(TLOG_DEBUG, "from %15s: error is %s\n", host, strerror(error)); } else if (result == PING_RESULT_END) { fast_ping_stop(ping_host); } } int _fast_ping_get_addr_by_type(PING_TYPE type, const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type) { switch (type) { case PING_TYPE_ICMP: return _fast_ping_get_addr_by_icmp(ip_str, port, out_gai, out_ping_type); break; case PING_TYPE_TCP: return _fast_ping_get_addr_by_tcp(ip_str, port, out_gai, out_ping_type); break; case PING_TYPE_TCP_SYN: return _fast_ping_get_addr_by_tcp_syn(ip_str, port, out_gai, out_ping_type); break; case PING_TYPE_DNS: return _fast_ping_get_addr_by_dns(ip_str, port, out_gai, out_ping_type); break; default: break; } return -1; } struct ping_host_struct *fast_ping_start(PING_TYPE type, const char *host, int count, int interval, int timeout, fast_ping_result ping_callback, void *userptr) { struct ping_host_struct *ping_host = NULL; struct addrinfo *gai = NULL; uint32_t addrkey = 0; char ip_str[PING_MAX_HOSTLEN]; int port = -1; FAST_PING_TYPE ping_type = FAST_PING_END; int ret = 0; struct fast_ping_fake_ip *fake = NULL; int fake_time_fd = -1; if (parse_ip(host, ip_str, &port) != 0) { goto errout; } if (timeout <= 0) { timeout = 100; } ret = _fast_ping_get_addr_by_type(type, ip_str, port, &gai, &ping_type); if (ret != 0) { goto errout; } ping_host = zalloc(1, sizeof(*ping_host)); if (ping_host == NULL) { goto errout; } safe_strncpy(ping_host->host, host, PING_MAX_HOSTLEN); ping_host->fd = -1; ping_host->timeout = timeout; ping_host->count = count; ping_host->type = ping_type; ping_host->userptr = userptr; atomic_set(&ping_host->ref, 0); atomic_set(&ping_host->notified, 0); ping_host->sid = atomic_inc_return(&ping_sid); ping_host->run = 0; if (ping_callback) { ping_host->ping_callback = ping_callback; } else { ping_host->ping_callback = _fast_ping_print_result; } ping_host->interval = (timeout > interval) ? timeout : interval; ping_host->addr_len = gai->ai_addrlen; ping_host->port = port; ping_host->ss_family = gai->ai_family; if (gai->ai_addrlen > sizeof(struct sockaddr_in6)) { goto errout; } memcpy(&ping_host->addr, gai->ai_addr, gai->ai_addrlen); tlog(TLOG_DEBUG, "ping %s, id = %d", host, ping_host->sid); fake = _fast_ping_fake_find(ping_host->type, gai->ai_addr, gai->ai_addrlen); if (fake) { fake_time_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (fake_time_fd < 0) { tlog(TLOG_ERROR, "timerfd_create failed, %s", strerror(errno)); goto errout; } /* already take ownership by find. */ ping_host->fake = fake; ping_host->fake_time_fd = fake_time_fd; fake = NULL; } addrkey = _fast_ping_hash_key(ping_host->sid, &ping_host->addr); _fast_ping_host_get(ping_host); _fast_ping_host_get(ping_host); // for ping race condition, get reference count twice if (_fast_ping_sendping(ping_host) != 0) { goto errout_remove; } pthread_mutex_lock(&ping.map_lock); _fast_ping_host_get(ping_host); if (hash_empty(ping.addrmap)) { _fast_ping_wakeup_thread(); } hash_add(ping.addrmap, &ping_host->addr_node, addrkey); ping_host->run = 1; pthread_mutex_unlock(&ping.map_lock); freeaddrinfo(gai); _fast_ping_host_put(ping_host); return ping_host; errout_remove: ping_host->ping_callback(ping_host, ping_host->host, PING_RESULT_ERROR, &ping_host->addr, ping_host->addr_len, ping_host->seq, ping_host->ttl, NULL, ping_host->error, ping_host->userptr); fast_ping_stop(ping_host); _fast_ping_host_put(ping_host); ping_host = NULL; errout: if (gai) { freeaddrinfo(gai); } if (ping_host) { free(ping_host); } if (fake_time_fd > 0) { close(fake_time_fd); } if (fake) { _fast_ping_fake_put(fake); } return NULL; } int fast_ping_stop(struct ping_host_struct *ping_host) { if (ping_host == NULL) { return 0; } atomic_inc_return(&ping_host->notified); _fast_ping_host_remove(ping_host); _fast_ping_host_put(ping_host); return 0; } void tv_sub(struct timeval *out, struct timeval *in) { if ((out->tv_usec -= in->tv_usec) < 0) { /* out -= in */ --out->tv_sec; out->tv_usec += 1000000; } out->tv_sec -= in->tv_sec; } static int _fast_ping_process(struct ping_host_struct *ping_host, struct epoll_event *event, struct timeval *now) { int ret = -1; if (ping_host->fake != NULL) { ret = _fast_ping_process_fake(ping_host, now); return ret; } switch (ping_host->type) { case FAST_PING_ICMP6: case FAST_PING_ICMP: ret = _fast_ping_process_icmp(ping_host, now); break; case FAST_PING_TCP: ret = _fast_ping_process_tcp(ping_host, event, now); break; case FAST_PING_TCP_SYN: ret = _fast_ping_process_tcp_syn(ping_host, now); break; case FAST_PING_UDP6: case FAST_PING_UDP: ret = _fast_ping_process_udp(ping_host, now); break; default: BUG("type error : %p, %d, %s, %d", ping_host, ping_host->sid, ping_host->host, ping_host->fd); break; } return ret; } static void _fast_ping_period_run(void) { struct ping_host_struct *ping_host = NULL; struct ping_host_struct *ping_host_tmp = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; struct timeval now; struct timezone tz; struct timeval interval; int64_t millisecond = 0; gettimeofday(&now, &tz); LIST_HEAD(action); pthread_mutex_lock(&ping.map_lock); hash_for_each_safe(ping.addrmap, i, tmp, ping_host, addr_node) { if (ping_host->run == 0) { continue; } interval = now; tv_sub(&interval, &ping_host->last); millisecond = interval.tv_sec * 1000 + interval.tv_usec / 1000; if (millisecond >= ping_host->timeout && ping_host->send == 1) { list_add_tail(&ping_host->action_list, &action); _fast_ping_host_get(ping_host); continue; } if (millisecond < ping_host->interval) { continue; } list_add_tail(&ping_host->action_list, &action); _fast_ping_host_get(ping_host); } pthread_mutex_unlock(&ping.map_lock); list_for_each_entry_safe(ping_host, ping_host_tmp, &action, action_list) { interval = now; tv_sub(&interval, &ping_host->last); millisecond = interval.tv_sec * 1000 + interval.tv_usec / 1000; if (millisecond >= ping_host->timeout && ping_host->send == 1) { _fast_ping_send_notify_event(ping_host, PING_RESULT_TIMEOUT, ping_host->seq, ping_host->ttl, &interval); ping_host->send = 0; } if (millisecond < ping_host->interval) { list_del_init(&ping_host->action_list); _fast_ping_host_put(ping_host); continue; } if (ping_host->count > 0) { if (ping_host->count == 1) { _fast_ping_host_remove(ping_host); list_del_init(&ping_host->action_list); _fast_ping_host_put(ping_host); continue; } ping_host->count--; } _fast_ping_sendping(ping_host); list_del_init(&ping_host->action_list); _fast_ping_host_put(ping_host); } } static void *_fast_ping_work(void *arg) { struct epoll_event events[PING_MAX_EVENTS + 1]; int num = 0; int i = 0; unsigned long now = {0}; struct timeval tvnow = {0}; int sleep = 100; int sleep_time = 0; unsigned long expect_time = 0; unsigned long start_time = 0; setpriority(PRIO_PROCESS, 0, -5); now = get_tick_count(); start_time = now; expect_time = now + sleep; while (atomic_read(&ping.run)) { now = get_tick_count(); if (now >= expect_time) { _fast_ping_period_run(); unsigned long elapsed_from_start = now - start_time; unsigned long next_period = (elapsed_from_start / sleep) + 1; expect_time = start_time + next_period * sleep; } sleep_time = (int)(expect_time - now); if (sleep_time < 0) { sleep_time = 0; } pthread_mutex_lock(&ping.map_lock); if (hash_empty(ping.addrmap)) { sleep_time = -1; } pthread_mutex_unlock(&ping.map_lock); num = epoll_wait(ping.epoll_fd, events, PING_MAX_EVENTS, sleep_time); if (num < 0) { usleep(100000); continue; } if (sleep_time == -1) { now = get_tick_count(); start_time = now; expect_time = now + sleep; } if (num == 0) { continue; } gettimeofday(&tvnow, NULL); for (i = 0; i < num; i++) { struct epoll_event *event = &events[i]; /* read event */ if (event->data.ptr == NULL) { uint64_t value; int unused __attribute__((unused)); unused = read(ping.event_fd, &value, sizeof(uint64_t)); continue; } struct ping_host_struct *ping_host = (struct ping_host_struct *)event->data.ptr; _fast_ping_process(ping_host, event, &tvnow); } } close(ping.epoll_fd); ping.epoll_fd = -1; return NULL; } int fast_ping_init(void) { pthread_attr_t attr; int epollfd = -1; int ret = 0; bool_print_log = 1; if (is_fast_ping_init == 1) { return -1; } if (ping.epoll_fd > 0) { return -1; } memset(&ping, 0, sizeof(ping)); pthread_attr_init(&attr); epollfd = epoll_create1(EPOLL_CLOEXEC); if (epollfd < 0) { tlog(TLOG_ERROR, "create epoll failed, %s\n", strerror(errno)); goto errout; } pthread_mutex_init(&ping.map_lock, NULL); pthread_mutex_init(&ping.lock, NULL); pthread_mutex_init(&ping.notify_lock, NULL); pthread_cond_init(&ping.notify_cond, NULL); INIT_LIST_HEAD(&ping.notify_event_list); hash_init(ping.addrmap); hash_init(ping.fake); ping.no_unprivileged_ping = !has_unprivileged_ping(); ping.ident = (getpid() & 0XFFFF); atomic_set(&ping.run, 1); ping.epoll_fd = epollfd; ret = pthread_create(&ping.tid, &attr, _fast_ping_work, NULL); if (ret != 0) { tlog(TLOG_ERROR, "create ping work thread failed, %s\n", strerror(ret)); goto errout; } ret = pthread_create(&ping.notify_tid, &attr, _fast_ping_notify_worker, NULL); if (ret != 0) { tlog(TLOG_ERROR, "create ping notifier work thread failed, %s\n", strerror(ret)); goto errout; } ret = _fast_ping_init_wakeup_event(); if (ret != 0) { tlog(TLOG_ERROR, "init wakeup event failed, %s\n", strerror(errno)); goto errout; } is_fast_ping_init = 1; return 0; errout: if (ping.notify_tid) { void *retval = NULL; atomic_set(&ping.run, 0); pthread_cond_signal(&ping.notify_cond); pthread_join(ping.notify_tid, &retval); ping.notify_tid = 0; } if (ping.tid) { void *retval = NULL; atomic_set(&ping.run, 0); _fast_ping_wakeup_thread(); pthread_join(ping.tid, &retval); ping.tid = 0; } if (epollfd > 0) { close(epollfd); ping.epoll_fd = -1; } if (ping.event_fd) { close(ping.event_fd); ping.event_fd = -1; } pthread_cond_destroy(&ping.notify_cond); pthread_mutex_destroy(&ping.notify_lock); pthread_mutex_destroy(&ping.lock); pthread_mutex_destroy(&ping.map_lock); memset(&ping, 0, sizeof(ping)); return -1; } static void _fast_ping_close_fds(void) { _fast_ping_close_icmp(); _fast_ping_close_udp(); _fast_ping_close_tcp_syn(); } void fast_ping_exit(void) { if (is_fast_ping_init == 0) { return; } if (ping.notify_tid) { void *retval = NULL; atomic_set(&ping.run, 0); pthread_cond_signal(&ping.notify_cond); pthread_join(ping.notify_tid, &retval); ping.notify_tid = 0; } if (ping.tid) { void *ret = NULL; atomic_set(&ping.run, 0); _fast_ping_wakeup_thread(); pthread_join(ping.tid, &ret); ping.tid = 0; } if (ping.event_fd > 0) { close(ping.event_fd); ping.event_fd = -1; } _fast_ping_close_fds(); _fast_ping_remove_all(); _fast_ping_remove_all_fake_ip(); _fast_ping_remove_all_notify_event(); pthread_cond_destroy(&ping.notify_cond); pthread_mutex_destroy(&ping.notify_lock); pthread_mutex_destroy(&ping.lock); pthread_mutex_destroy(&ping.map_lock); is_fast_ping_init = 0; } ================================================ FILE: src/fast_ping/fast_ping.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_H_ #define _FAST_PING_H_ #define _GNU_SOURCE #include "smartdns/fast_ping.h" #include "smartdns/lib/atomic.h" #include "smartdns/lib/hashtable.h" #include "smartdns/lib/list.h" #include "smartdns/tlog.h" #include #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ #define PING_MAX_EVENTS 128 #define PING_MAX_HOSTLEN 128 #define ICMP_PACKET_SIZE (1024 * 64) #define ICMP_INPACKET_SIZE 1024 #define IPV4_ADDR_LEN 4 #define IPV6_ADDR_LEN 16 #define SOCKET_PRIORITY (6) #ifndef ICMP_FILTER #define ICMP_FILTER 1 struct icmp_filter { uint32_t data; }; #endif struct ping_dns_head { unsigned short id; unsigned short flag; unsigned short qdcount; unsigned short ancount; unsigned short nscount; unsigned short nrcount; char qd_name; unsigned short q_qtype; unsigned short q_qclass; } __attribute__((packed)); typedef enum FAST_PING_TYPE { FAST_PING_ICMP = 1, FAST_PING_ICMP6 = 2, FAST_PING_TCP, FAST_PING_TCP_SYN, FAST_PING_UDP, FAST_PING_UDP6, FAST_PING_END, } FAST_PING_TYPE; struct fast_ping_packet_msg { struct timeval tv; unsigned int sid; unsigned int seq; }; struct fast_ping_packet { union { struct icmp icmp; struct icmp6_hdr icmp6; }; unsigned int ttl; struct fast_ping_packet_msg msg; }; struct fast_ping_fake_ip { struct hlist_node node; atomic_t ref; PING_TYPE type; FAST_PING_TYPE ping_type; char host[PING_MAX_HOSTLEN]; int ttl; float time; struct sockaddr_storage addr; int addr_len; }; struct ping_host_struct { atomic_t ref; atomic_t notified; struct hlist_node addr_node; struct list_head action_list; FAST_PING_TYPE type; void *userptr; int error; fast_ping_result ping_callback; char host[PING_MAX_HOSTLEN]; int fd; unsigned short seq; int ttl; struct timeval last; int interval; int timeout; int count; int send; int run; unsigned short sid; unsigned short port; unsigned short tcp_local_port; unsigned short ss_family; union { struct sockaddr addr; struct sockaddr_in6 in6; struct sockaddr_in in; }; socklen_t addr_len; struct fast_ping_packet packet; struct fast_ping_packet recv_packet_buffer; struct fast_ping_fake_ip *fake; int fake_time_fd; }; struct fast_ping_notify_event { struct list_head list; struct ping_host_struct *ping_host; FAST_PING_RESULT ping_result; unsigned int seq; int ttl; struct timeval tvresult; }; struct fast_ping_struct { atomic_t run; pthread_t tid; pthread_mutex_t lock; unsigned short ident; int epoll_fd; int no_unprivileged_ping; int fd_icmp; struct ping_host_struct icmp_host; int fd_icmp6; struct ping_host_struct icmp6_host; int fd_udp; struct ping_host_struct udp_host; int fd_udp6; struct ping_host_struct udp6_host; int fd_tcp_syn; struct ping_host_struct tcp_syn_host; int fd_tcp_syn6; struct ping_host_struct tcp_syn6_host; int fd_tcp_syn_bind; uint16_t tcp_syn_bind_port; struct sockaddr_in tcp_syn_bind_addr; int fd_tcp_syn6_bind; uint16_t tcp_syn6_bind_port; struct sockaddr_in6 tcp_syn6_bind_addr; int event_fd; pthread_t notify_tid; pthread_cond_t notify_cond; pthread_mutex_t notify_lock; struct list_head notify_event_list; pthread_mutex_t map_lock; DECLARE_HASHTABLE(addrmap, 6); DECLARE_HASHTABLE(fake, 6); int fake_ip_num; }; extern struct fast_ping_struct ping; extern int bool_print_log; uint32_t _fast_ping_hash_key(unsigned int sid, struct sockaddr *addr); struct addrinfo *_fast_ping_getaddr(const char *host, const char *port, int type, int protocol); int _fast_ping_get_addr_by_type(PING_TYPE type, const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type); void tv_sub(struct timeval *out, struct timeval *in); int _fast_ping_getdomain(const char *host); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_H_ ================================================ FILE: src/fast_ping/notify_event.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "notify_event.h" #include "ping_host.h" #include "smartdns/util.h" #include #include #include #include #include static void _fast_ping_release_notify_event(struct fast_ping_notify_event *ping_notify_event) { pthread_mutex_lock(&ping.notify_lock); list_del_init(&ping_notify_event->list); pthread_mutex_unlock(&ping.notify_lock); if (ping_notify_event->ping_host) { _fast_ping_host_put(ping_notify_event->ping_host); ping_notify_event->ping_host = NULL; } free(ping_notify_event); } int _fast_ping_send_notify_event(struct ping_host_struct *ping_host, FAST_PING_RESULT ping_result, unsigned int seq, int ttl, struct timeval *tvresult) { struct fast_ping_notify_event *notify_event = NULL; notify_event = zalloc(1, sizeof(struct fast_ping_notify_event)); if (notify_event == NULL) { goto errout; } INIT_LIST_HEAD(¬ify_event->list); notify_event->seq = seq; notify_event->ttl = ttl; notify_event->ping_result = ping_result; notify_event->tvresult = *tvresult; pthread_mutex_lock(&ping.notify_lock); if (list_empty(&ping.notify_event_list)) { pthread_cond_signal(&ping.notify_cond); } list_add_tail(¬ify_event->list, &ping.notify_event_list); notify_event->ping_host = ping_host; _fast_ping_host_get(ping_host); pthread_mutex_unlock(&ping.notify_lock); return 0; errout: if (notify_event) { _fast_ping_release_notify_event(notify_event); } return -1; } static void _fast_ping_process_notify_event(struct fast_ping_notify_event *ping_notify_event) { struct ping_host_struct *ping_host = ping_notify_event->ping_host; if (ping_host == NULL) { return; } ping_host->ping_callback(ping_host, ping_host->host, ping_notify_event->ping_result, &ping_host->addr, ping_host->addr_len, ping_notify_event->seq, ping_notify_event->ttl, &ping_notify_event->tvresult, ping_host->error, ping_host->userptr); } void *_fast_ping_notify_worker(void *arg) { struct fast_ping_notify_event *ping_notify_event = NULL; while (atomic_read(&ping.run)) { pthread_mutex_lock(&ping.notify_lock); if (list_empty(&ping.notify_event_list)) { pthread_cond_wait(&ping.notify_cond, &ping.notify_lock); } ping_notify_event = list_first_entry_or_null(&ping.notify_event_list, struct fast_ping_notify_event, list); if (ping_notify_event) { list_del_init(&ping_notify_event->list); } pthread_mutex_unlock(&ping.notify_lock); if (ping_notify_event == NULL) { continue; } _fast_ping_process_notify_event(ping_notify_event); _fast_ping_release_notify_event(ping_notify_event); } return NULL; } void _fast_ping_remove_all_notify_event(void) { struct fast_ping_notify_event *notify_event = NULL; struct fast_ping_notify_event *tmp = NULL; list_for_each_entry_safe(notify_event, tmp, &ping.notify_event_list, list) { _fast_ping_process_notify_event(notify_event); _fast_ping_release_notify_event(notify_event); } } ================================================ FILE: src/fast_ping/notify_event.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_NOTIFY_EVENT_H_ #define _FAST_PING_NOTIFY_EVENT_H_ #include "fast_ping.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _fast_ping_remove_all_notify_event(void); void *_fast_ping_notify_worker(void *arg); int _fast_ping_send_notify_event(struct ping_host_struct *ping_host, FAST_PING_RESULT ping_result, unsigned int seq, int ttl, struct timeval *tvresult); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_NOTIFY_EVENT_H_ ================================================ FILE: src/fast_ping/ping_fake.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/lib/stringutil.h" #include "smartdns/util.h" #include "notify_event.h" #include "ping_fake.h" #include "ping_host.h" #include #include #include #include #include void _fast_ping_fake_put(struct fast_ping_fake_ip *fake) { int ref_cnt = atomic_dec_and_test(&fake->ref); if (!ref_cnt) { if (ref_cnt < 0) { BUG("invalid refcount of fake ping %s", fake->host); } return; } pthread_mutex_lock(&ping.map_lock); if (hash_hashed(&fake->node)) { hash_del(&fake->node); } pthread_mutex_unlock(&ping.map_lock); free(fake); } void _fast_ping_fake_remove(struct fast_ping_fake_ip *fake) { pthread_mutex_lock(&ping.map_lock); if (hash_hashed(&fake->node)) { hash_del(&fake->node); } pthread_mutex_unlock(&ping.map_lock); _fast_ping_fake_put(fake); } void _fast_ping_fake_get(struct fast_ping_fake_ip *fake) { atomic_inc(&fake->ref); } struct fast_ping_fake_ip *_fast_ping_fake_find(FAST_PING_TYPE ping_type, struct sockaddr *addr, int addr_len) { struct fast_ping_fake_ip *fake = NULL; struct fast_ping_fake_ip *ret = NULL; uint32_t key = 0; if (ping.fake_ip_num == 0) { return NULL; } key = jhash(addr, addr_len, 0); key = jhash(&ping_type, sizeof(ping_type), key); pthread_mutex_lock(&ping.map_lock); hash_for_each_possible(ping.fake, fake, node, key) { if (fake->ping_type != ping_type) { continue; } if (fake->addr_len != addr_len) { continue; } if (memcmp(&fake->addr, addr, fake->addr_len) != 0) { continue; } ret = fake; _fast_ping_fake_get(fake); break; } pthread_mutex_unlock(&ping.map_lock); return ret; } int fast_ping_fake_ip_add(PING_TYPE type, const char *host, int ttl, float time) { struct fast_ping_fake_ip *fake = NULL; struct fast_ping_fake_ip *fake_old = NULL; char ip_str[PING_MAX_HOSTLEN]; int port = -1; FAST_PING_TYPE ping_type = FAST_PING_END; uint32_t key = 0; int ret = -1; struct addrinfo *gai = NULL; if (parse_ip(host, ip_str, &port) != 0) { goto errout; } ret = _fast_ping_get_addr_by_type(type, ip_str, port, &gai, &ping_type); if (ret != 0) { goto errout; } fake_old = _fast_ping_fake_find(ping_type, gai->ai_addr, gai->ai_addrlen); fake = zalloc(1, sizeof(*fake)); if (fake == NULL) { goto errout; } safe_strncpy(fake->host, ip_str, PING_MAX_HOSTLEN); fake->ttl = ttl; fake->time = time; fake->type = type; fake->ping_type = ping_type; memcpy(&fake->addr, gai->ai_addr, gai->ai_addrlen); fake->addr_len = gai->ai_addrlen; INIT_HLIST_NODE(&fake->node); atomic_set(&fake->ref, 1); key = jhash(&fake->addr, fake->addr_len, 0); key = jhash(&ping_type, sizeof(ping_type), key); pthread_mutex_lock(&ping.map_lock); hash_add(ping.fake, &fake->node, key); pthread_mutex_unlock(&ping.map_lock); ping.fake_ip_num++; if (fake_old != NULL) { _fast_ping_fake_put(fake_old); _fast_ping_fake_remove(fake_old); } freeaddrinfo(gai); return 0; errout: if (fake != NULL) { free(fake); } if (fake_old != NULL) { _fast_ping_fake_put(fake_old); } if (gai != NULL) { freeaddrinfo(gai); } return -1; } int fast_ping_fake_ip_remove(PING_TYPE type, const char *host) { struct fast_ping_fake_ip *fake = NULL; char ip_str[PING_MAX_HOSTLEN]; int port = -1; int ret = -1; FAST_PING_TYPE ping_type = FAST_PING_END; struct addrinfo *gai = NULL; if (parse_ip(host, ip_str, &port) != 0) { return -1; } ret = _fast_ping_get_addr_by_type(type, ip_str, port, &gai, &ping_type); if (ret != 0) { goto errout; } fake = _fast_ping_fake_find(ping_type, gai->ai_addr, gai->ai_addrlen); if (fake == NULL) { goto errout; } _fast_ping_fake_remove(fake); _fast_ping_fake_put(fake); ping.fake_ip_num--; freeaddrinfo(gai); return 0; errout: if (gai != NULL) { freeaddrinfo(gai); } return -1; } int _fast_ping_send_fake(struct ping_host_struct *ping_host, struct fast_ping_fake_ip *fake) { struct itimerspec its; int sec = fake->time / 1000; int cent_usec = ((long)(fake->time * 10)) % 10000; its.it_value.tv_sec = sec; its.it_value.tv_nsec = cent_usec * 1000 * 100; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0; if (timerfd_settime(ping_host->fake_time_fd, 0, &its, NULL) < 0) { tlog(TLOG_ERROR, "timerfd_settime failed, %s", strerror(errno)); goto errout; } struct epoll_event ev; ev.events = EPOLLIN; ev.data.ptr = ping_host; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, ping_host->fake_time_fd, &ev) == -1) { if (errno != EEXIST) { goto errout; } } ping_host->seq++; return 0; errout: return -1; } int _fast_ping_process_fake(struct ping_host_struct *ping_host, struct timeval *now) { struct timeval tvresult = *now; struct timeval *tvsend = &ping_host->last; uint64_t exp; int ret; ret = read(ping_host->fake_time_fd, &exp, sizeof(uint64_t)); if (ret < 0) { return -1; } ping_host->ttl = ping_host->fake->ttl; tv_sub(&tvresult, tvsend); if (ping_host->ping_callback) { _fast_ping_send_notify_event(ping_host, PING_RESULT_RESPONSE, ping_host->seq, ping_host->ttl, &tvresult); } ping_host->send = 0; if (ping_host->count == 1) { _fast_ping_host_remove(ping_host); } return 0; } void _fast_ping_remove_all_fake_ip(void) { struct fast_ping_fake_ip *fake = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; hash_for_each_safe(ping.fake, i, tmp, fake, node) { _fast_ping_fake_put(fake); } } ================================================ FILE: src/fast_ping/ping_fake.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_FAKE_H_ #define _FAST_PING_FAKE_H_ #include "fast_ping.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _fast_ping_fake_put(struct fast_ping_fake_ip *fake); void _fast_ping_fake_remove(struct fast_ping_fake_ip *fake); void _fast_ping_fake_get(struct fast_ping_fake_ip *fake); struct fast_ping_fake_ip *_fast_ping_fake_find(FAST_PING_TYPE ping_type, struct sockaddr *addr, int addr_len); void _fast_ping_remove_all_fake_ip(void); int _fast_ping_process_fake(struct ping_host_struct *ping_host, struct timeval *now); int _fast_ping_send_fake(struct ping_host_struct *ping_host, struct fast_ping_fake_ip *fake); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_H_ ================================================ FILE: src/fast_ping/ping_host.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "ping_host.h" #include "notify_event.h" #include "ping_fake.h" #include "smartdns/util.h" #include #include #include #include void _fast_ping_host_get(struct ping_host_struct *ping_host) { if (atomic_inc_return(&ping_host->ref) <= 0) { BUG("ping host ref is invalid, host: %s", ping_host->host); } } void _fast_ping_close_host_sock(struct ping_host_struct *ping_host) { if (ping_host->fake_time_fd > 0) { struct epoll_event *event = NULL; event = (struct epoll_event *)1; epoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping_host->fake_time_fd, event); close(ping_host->fake_time_fd); ping_host->fake_time_fd = -1; } if (ping_host->fd < 0) { return; } struct epoll_event *event = NULL; event = (struct epoll_event *)1; epoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping_host->fd, event); close(ping_host->fd); ping_host->fd = -1; } void _fast_ping_host_put(struct ping_host_struct *ping_host) { int ref_cnt = atomic_dec_and_test(&ping_host->ref); if (!ref_cnt) { if (ref_cnt < 0) { BUG("invalid refcount of ping_host %s", ping_host->host); } return; } _fast_ping_close_host_sock(ping_host); if (ping_host->fake != NULL) { _fast_ping_fake_put(ping_host->fake); ping_host->fake = NULL; } pthread_mutex_lock(&ping.map_lock); hash_del(&ping_host->addr_node); pthread_mutex_unlock(&ping.map_lock); if (atomic_inc_return(&ping_host->notified) == 1) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; _fast_ping_send_notify_event(ping_host, PING_RESULT_END, ping_host->seq, ping_host->ttl, &tv); } tlog(TLOG_DEBUG, "ping %s end, id %d", ping_host->host, ping_host->sid); ping_host->type = FAST_PING_END; free(ping_host); } void _fast_ping_host_remove(struct ping_host_struct *ping_host) { _fast_ping_close_host_sock(ping_host); pthread_mutex_lock(&ping.map_lock); if (!hash_hashed(&ping_host->addr_node)) { pthread_mutex_unlock(&ping.map_lock); return; } hash_del(&ping_host->addr_node); pthread_mutex_unlock(&ping.map_lock); if (atomic_inc_return(&ping_host->notified) == 1) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; _fast_ping_send_notify_event(ping_host, PING_RESULT_END, ping_host->seq, ping_host->ttl, &tv); } _fast_ping_host_put(ping_host); } void _fast_ping_remove_all(void) { struct ping_host_struct *ping_host = NULL; struct ping_host_struct *ping_host_tmp = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; LIST_HEAD(remove_list); pthread_mutex_lock(&ping.map_lock); hash_for_each_safe(ping.addrmap, i, tmp, ping_host, addr_node) { list_add_tail(&ping_host->action_list, &remove_list); } pthread_mutex_unlock(&ping.map_lock); list_for_each_entry_safe(ping_host, ping_host_tmp, &remove_list, action_list) { _fast_ping_host_remove(ping_host); } } ================================================ FILE: src/fast_ping/ping_host.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_HOST_H_ #define _FAST_PING_HOST_H_ #include "fast_ping.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _fast_ping_host_remove(struct ping_host_struct *ping_host); void _fast_ping_host_put(struct ping_host_struct *ping_host); void _fast_ping_host_get(struct ping_host_struct *ping_host); void _fast_ping_close_host_sock(struct ping_host_struct *ping_host); void _fast_ping_remove_all(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_HOST_H_ ================================================ FILE: src/fast_ping/ping_icmp.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/util.h" #include "notify_event.h" #include "ping_host.h" #include "ping_icmp.h" #include "ping_icmp6.h" #include #include #include #include #include static void _fast_ping_install_filter_v4(int sock) { static int once; static struct sock_filter insns[] = { BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0), /* Skip IP header. F..g BSD... Look into ping6. */ BPF_STMT(BPF_LD | BPF_H | BPF_IND, 4), /* Load icmp echo ident */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0xAAAA, 0, 1), /* Ours? */ BPF_STMT(BPF_RET | BPF_K, ~0U), /* Yes, it passes. */ BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), /* Load icmp type */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */ BPF_STMT(BPF_RET | BPF_K, 0xFFFFFFF), /* No. It passes. */ BPF_STMT(BPF_RET | BPF_K, 0) /* Echo with wrong ident. Reject. */ }; static struct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns}; if (once) { return; } once = 1; /* Patch bpflet for current identifier. */ insns[2] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(ping.ident), 0, 1); if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) { tlog(TLOG_WARN, "WARNING: failed to install socket filter\n"); } } int _fast_ping_sendping_v4(struct ping_host_struct *ping_host) { if (_fast_ping_icmp_create_socket(ping_host) < 0) { goto errout; } if (ping.fd_icmp <= 0) { errno = EADDRNOTAVAIL; goto errout; } struct fast_ping_packet *packet = &ping_host->packet; struct icmp *icmp = &packet->icmp; int len = 0; ping_host->seq++; memset(icmp, 0, sizeof(*icmp)); icmp->icmp_type = ICMP_ECHO; icmp->icmp_code = 0; icmp->icmp_cksum = 0; icmp->icmp_id = ping.ident; icmp->icmp_seq = htons(ping_host->seq); gettimeofday(&packet->msg.tv, NULL); gettimeofday(&ping_host->last, NULL); packet->msg.sid = ping_host->sid; packet->msg.seq = ping_host->seq; icmp->icmp_cksum = _fast_ping_checksum((void *)packet, sizeof(struct fast_ping_packet)); len = sendto(ping.fd_icmp, packet, sizeof(struct fast_ping_packet), 0, &ping_host->addr, ping_host->addr_len); if (len != sizeof(struct fast_ping_packet)) { int err = errno; if (errno == EAGAIN || errno == EWOULDBLOCK) { goto errout; } if (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL || errno == EPERM || errno == EACCES) { goto errout; } char ping_host_name[PING_MAX_HOSTLEN]; tlog(TLOG_ERROR, "sendto %s, id %d, %s", get_host_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr), ping_host->sid, strerror(err)); goto errout; } return 0; errout: return -1; } static int _fast_ping_create_icmp_sock(FAST_PING_TYPE type) { int fd = -1; struct ping_host_struct *icmp_host = NULL; struct epoll_event event; /* Set receive and send buffer to 512KB, if buffer size is too small, ping may fail. */ int buffsize = 512 * 1024; socklen_t optlen = sizeof(buffsize); const int val = 255; const int on = 1; const int ip_tos = (IPTOS_LOWDELAY | IPTOS_RELIABILITY); switch (type) { case FAST_PING_ICMP: if (ping.no_unprivileged_ping == 0) { fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); } else { fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (fd > 0) { _fast_ping_install_filter_v4(fd); } } if (fd < 0) { if (errno == EACCES || errno == EAFNOSUPPORT) { if (bool_print_log == 0) { goto errout; } bool_print_log = 0; } tlog(TLOG_ERROR, "create icmp socket failed, %s\n", strerror(errno)); goto errout; } icmp_host = &ping.icmp_host; break; case FAST_PING_ICMP6: if (ping.no_unprivileged_ping == 0) { fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); } else { fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (fd > 0) { _fast_ping_install_filter_v6(fd); } } if (fd < 0) { if (errno == EACCES || errno == EAFNOSUPPORT) { if (bool_print_log == 0) { goto errout; } bool_print_log = 0; } tlog(TLOG_INFO, "create icmpv6 socket failed, %s\n", strerror(errno)); goto errout; } setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)); setsockopt(fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on)); setsockopt(fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on)); icmp_host = &ping.icmp6_host; break; default: return -1; } struct icmp_filter filt; filt.data = ~((1 << ICMP_SOURCE_QUENCH) | (1 << ICMP_DEST_UNREACH) | (1 << ICMP_TIME_EXCEEDED) | (1 << ICMP_PARAMETERPROB) | (1 << ICMP_REDIRECT) | (1 << ICMP_ECHOREPLY)); setsockopt(fd, SOL_RAW, ICMP_FILTER, &filt, sizeof filt); setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const char *)&buffsize, optlen); setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char *)&buffsize, optlen); setsockopt(fd, SOL_IP, IP_TTL, &val, sizeof(val)); setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); set_fd_nonblock(fd, 1); icmp_host->fd = fd; icmp_host->type = type; memset(&event, 0, sizeof(event)); event.events = EPOLLIN; event.data.ptr = icmp_host; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { goto errout; } return fd; errout: close(fd); if (icmp_host) { icmp_host->fd = -1; icmp_host->type = 0; } return -1; } static int _fast_ping_create_icmp(FAST_PING_TYPE type) { int fd = -1; int *set_fd = NULL; pthread_mutex_lock(&ping.lock); switch (type) { case FAST_PING_ICMP: set_fd = &ping.fd_icmp; break; case FAST_PING_ICMP6: set_fd = &ping.fd_icmp6; break; default: goto errout; break; } if (*set_fd > 0) { goto out; } fd = _fast_ping_create_icmp_sock(type); if (fd < 0) { goto errout; } *set_fd = fd; out: pthread_mutex_unlock(&ping.lock); return *set_fd; errout: if (fd > 0) { close(fd); } pthread_mutex_unlock(&ping.lock); return -1; } int _fast_ping_icmp_create_socket(struct ping_host_struct *ping_host) { if (_fast_ping_create_icmp(ping_host->type) < 0) { goto errout; } return 0; errout: return -1; } struct fast_ping_packet *_fast_ping_icmp_packet(struct ping_host_struct *ping_host, struct msghdr *msg, u_char *packet_data, int data_len) { struct ip *ip = (struct ip *)packet_data; struct fast_ping_packet *packet = NULL; struct icmp *icmp = NULL; int hlen = 0; int icmp_len = 0; if (ping.no_unprivileged_ping) { hlen = ip->ip_hl << 2; if (ip->ip_p != IPPROTO_ICMP) { tlog(TLOG_DEBUG, "ip type failed, %d:%d", ip->ip_p, IPPROTO_ICMP); return NULL; } } if (data_len - hlen < (int)sizeof(struct icmp)) { tlog(TLOG_DEBUG, "response ping package length is invalid, len: %d", data_len); return NULL; } int align = __alignof__(struct fast_ping_packet); if (((uintptr_t)(packet_data + hlen) % align) == 0 && ping.no_unprivileged_ping == 0) { packet = (struct fast_ping_packet *)(packet_data + hlen); } else { int copy_len = sizeof(ping_host->recv_packet_buffer); if (copy_len > data_len - hlen) { copy_len = data_len - hlen; } memcpy(&ping_host->recv_packet_buffer, packet_data + hlen, copy_len); packet = &ping_host->recv_packet_buffer; } icmp = &packet->icmp; icmp_len = data_len - hlen; if (icmp_len < 16) { tlog(TLOG_ERROR, "length is invalid, %d", icmp_len); return NULL; } if (icmp->icmp_type != ICMP_ECHOREPLY) { errno = ENETUNREACH; return NULL; } if (icmp->icmp_id != ping.ident && ping.no_unprivileged_ping) { tlog(TLOG_WARN, "ident failed, %d:%d", icmp->icmp_id, ping.ident); return NULL; } packet->ttl = ip->ip_ttl; return packet; } static struct fast_ping_packet *_fast_ping_recv_packet(struct ping_host_struct *ping_host, struct msghdr *msg, u_char *inpacket, int len, struct timeval *tvrecv) { struct fast_ping_packet *packet = NULL; if (ping_host->type == FAST_PING_ICMP6) { packet = _fast_ping_icmp6_packet(ping_host, msg, inpacket, len); if (packet == NULL) { goto errout; } } else if (ping_host->type == FAST_PING_ICMP) { packet = _fast_ping_icmp_packet(ping_host, msg, inpacket, len); if (packet == NULL) { goto errout; } } else { tlog(TLOG_ERROR, "ping host type is invalid, %d", ping_host->type); goto errout; } return packet; errout: return NULL; } int _fast_ping_process_icmp(struct ping_host_struct *ping_host, struct timeval *now) { int len = 0; u_char inpacket[ICMP_INPACKET_SIZE]; struct sockaddr_storage from; struct ping_host_struct *recv_ping_host = NULL; struct fast_ping_packet *packet = NULL; socklen_t from_len = sizeof(from); uint32_t addrkey = 0; struct timeval tvresult = *now; struct timeval *tvsend = NULL; unsigned int sid = 0; unsigned int seq = 0; struct msghdr msg; struct iovec iov; char ans_data[4096]; memset(&msg, 0, sizeof(msg)); iov.iov_base = (char *)inpacket; iov.iov_len = sizeof(inpacket); msg.msg_name = &from; msg.msg_namelen = sizeof(from); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = ans_data; msg.msg_controllen = sizeof(ans_data); len = recvmsg(ping_host->fd, &msg, MSG_DONTWAIT); if (len < 0) { tlog(TLOG_ERROR, "recvfrom failed, %s\n", strerror(errno)); goto errout; } from_len = msg.msg_namelen; packet = _fast_ping_recv_packet(ping_host, &msg, inpacket, len, now); if (packet == NULL) { char name[PING_MAX_HOSTLEN]; if (errno == ENETUNREACH) { goto errout; } tlog(TLOG_DEBUG, "recv ping packet from %s failed.", get_host_by_addr(name, sizeof(name), (struct sockaddr *)&from)); goto errout; } tvsend = &packet->msg.tv; sid = packet->msg.sid; seq = packet->msg.seq; addrkey = _fast_ping_hash_key(sid, (struct sockaddr *)&from); pthread_mutex_lock(&ping.map_lock); hash_for_each_possible(ping.addrmap, recv_ping_host, addr_node, addrkey) { if (_fast_ping_sockaddr_ip_cmp(&recv_ping_host->addr, recv_ping_host->addr_len, (struct sockaddr *)&from, from_len) == 0 && recv_ping_host->sid == sid) { _fast_ping_host_get(recv_ping_host); break; } } pthread_mutex_unlock(&ping.map_lock); if (recv_ping_host == NULL) { return -1; } if (recv_ping_host->seq != seq) { tlog(TLOG_ERROR, "seq num mismatch, expect %u, real %u", recv_ping_host->seq, seq); _fast_ping_host_put(recv_ping_host); return -1; } recv_ping_host->ttl = packet->ttl; tv_sub(&tvresult, tvsend); if (recv_ping_host->ping_callback) { _fast_ping_send_notify_event(recv_ping_host, PING_RESULT_RESPONSE, recv_ping_host->seq, recv_ping_host->ttl, &tvresult); } recv_ping_host->send = 0; if (recv_ping_host->count == 1) { _fast_ping_host_remove(recv_ping_host); } _fast_ping_host_put(recv_ping_host); return 0; errout: return -1; } int _fast_ping_get_addr_by_icmp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type) { struct addrinfo *gai = NULL; int socktype = 0; int domain = -1; FAST_PING_TYPE ping_type = 0; int sockproto = 0; char *service = NULL; socktype = SOCK_RAW; domain = _fast_ping_getdomain(ip_str); if (domain < 0) { goto errout; } switch (domain) { case AF_INET: sockproto = IPPROTO_ICMP; ping_type = FAST_PING_ICMP; break; case AF_INET6: sockproto = IPPROTO_ICMPV6; ping_type = FAST_PING_ICMP6; break; default: goto errout; break; } if (out_gai != NULL) { gai = _fast_ping_getaddr(ip_str, service, socktype, sockproto); if (gai == NULL) { goto errout; } *out_gai = gai; } if (out_ping_type != NULL) { *out_ping_type = ping_type; } return 0; errout: if (gai) { freeaddrinfo(gai); } return -1; } int _fast_ping_sockaddr_ip_cmp(struct sockaddr *first_addr, socklen_t first_addr_len, struct sockaddr *second_addr, socklen_t second_addr_len) { void *ip1, *ip2; int len1, len2; if (first_addr->sa_family == AF_INET) { ip1 = &((struct sockaddr_in *)first_addr)->sin_addr.s_addr; len1 = IPV4_ADDR_LEN; } else if (first_addr->sa_family == AF_INET6) { struct in6_addr *in6 = &((struct sockaddr_in6 *)first_addr)->sin6_addr; if (IN6_IS_ADDR_V4MAPPED(in6)) { ip1 = in6->s6_addr + 12; len1 = IPV4_ADDR_LEN; } else { ip1 = in6->s6_addr; len1 = IPV6_ADDR_LEN; } } else { return -1; } if (second_addr->sa_family == AF_INET) { ip2 = &((struct sockaddr_in *)second_addr)->sin_addr.s_addr; len2 = IPV4_ADDR_LEN; } else if (second_addr->sa_family == AF_INET6) { struct in6_addr *in6 = &((struct sockaddr_in6 *)second_addr)->sin6_addr; if (IN6_IS_ADDR_V4MAPPED(in6)) { ip2 = in6->s6_addr + 12; len2 = IPV4_ADDR_LEN; } else { ip2 = in6->s6_addr; len2 = IPV6_ADDR_LEN; } } else { return -1; } if (len1 != len2) { return -1; } return memcmp(ip1, ip2, len1); } uint16_t _fast_ping_checksum(uint16_t *header, size_t len) { uint32_t sum = 0; unsigned int i = 0; for (i = 0; i < len / sizeof(uint16_t); i++) { sum += ntohs(header[i]); } return htons(~((sum >> 16) + (sum & 0xffff))); } void _fast_ping_close_icmp(void) { if (ping.fd_icmp > 0) { epoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping.fd_icmp, NULL); close(ping.fd_icmp); ping.fd_icmp = -1; } if (ping.fd_icmp6 > 0) { epoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping.fd_icmp6, NULL); close(ping.fd_icmp6); ping.fd_icmp6 = -1; } } ================================================ FILE: src/fast_ping/ping_icmp.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_ICMP_H_ #define _FAST_PING_ICMP_H_ #include "fast_ping.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _fast_ping_sendping_v4(struct ping_host_struct *ping_host); struct fast_ping_packet *_fast_ping_icmp_packet(struct ping_host_struct *ping_host, struct msghdr *msg, u_char *packet_data, int data_len); int _fast_ping_sockaddr_ip_cmp(struct sockaddr *first_addr, socklen_t first_addr_len, struct sockaddr *second_addr, socklen_t second_addr_len); uint16_t _fast_ping_checksum(uint16_t *header, size_t len); int _fast_ping_icmp_create_socket(struct ping_host_struct *ping_host); int _fast_ping_process_icmp(struct ping_host_struct *ping_host, struct timeval *now); int _fast_ping_get_addr_by_icmp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type); void _fast_ping_close_icmp(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_ICMP_H_ ================================================ FILE: src/fast_ping/ping_icmp6.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/util.h" #include "ping_icmp.h" #include "ping_icmp6.h" #include #include #include #include void _fast_ping_install_filter_v6(int sock) { struct icmp6_filter icmp6_filter; ICMP6_FILTER_SETBLOCKALL(&icmp6_filter); ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &icmp6_filter); setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &icmp6_filter, sizeof(struct icmp6_filter)); static int once; static struct sock_filter insns[] = { BPF_STMT(BPF_LD | BPF_H | BPF_ABS, 4), /* Load icmp echo ident */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0xAAAA, 0, 1), /* Ours? */ BPF_STMT(BPF_RET | BPF_K, ~0U), /* Yes, it passes. */ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, 0), /* Load icmp type */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP6_ECHO_REPLY, 1, 0), /* Echo? */ BPF_STMT(BPF_RET | BPF_K, ~0U), /* No. It passes. This must not happen. */ BPF_STMT(BPF_RET | BPF_K, 0), /* Echo with wrong ident. Reject. */ }; static struct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns}; if (once) { return; } once = 1; /* Patch bpflet for current identifier. */ insns[1] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(ping.ident), 0, 1); if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) { tlog(TLOG_WARN, "WARNING: failed to install socket filter\n"); } } int _fast_ping_sendping_v6(struct ping_host_struct *ping_host) { struct fast_ping_packet *packet = &ping_host->packet; struct icmp6_hdr *icmp6 = &packet->icmp6; int len = 0; if (_fast_ping_icmp_create_socket(ping_host) < 0) { goto errout; } if (ping.fd_icmp6 <= 0) { errno = EADDRNOTAVAIL; goto errout; } ping_host->seq++; memset(icmp6, 0, sizeof(*icmp6)); icmp6->icmp6_type = ICMP6_ECHO_REQUEST; icmp6->icmp6_code = 0; icmp6->icmp6_cksum = 0; icmp6->icmp6_id = ping.ident; icmp6->icmp6_seq = htons(ping_host->seq); gettimeofday(&packet->msg.tv, NULL); gettimeofday(&ping_host->last, NULL); packet->msg.sid = ping_host->sid; packet->msg.seq = ping_host->seq; icmp6->icmp6_cksum = _fast_ping_checksum((void *)packet, sizeof(struct fast_ping_packet)); len = sendto(ping.fd_icmp6, &ping_host->packet, sizeof(struct fast_ping_packet), 0, &ping_host->addr, ping_host->addr_len); if (len != sizeof(struct fast_ping_packet)) { int err = errno; switch (err) { case ENETUNREACH: case EINVAL: case EADDRNOTAVAIL: case EHOSTUNREACH: case ENOBUFS: case EACCES: case EPERM: case EAFNOSUPPORT: case EAGAIN: #if EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif goto errout; default: break; } if (is_private_addr_sockaddr(&ping_host->addr, ping_host->addr_len)) { goto errout; } char ping_host_name[PING_MAX_HOSTLEN]; tlog(TLOG_WARN, "sendto %s, id %d, %s", get_host_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr), ping_host->sid, strerror(err)); goto errout; } return 0; errout: return -1; } struct fast_ping_packet *_fast_ping_icmp6_packet(struct ping_host_struct *ping_host, struct msghdr *msg, u_char *packet_data, int data_len) { int icmp_len = 0; struct fast_ping_packet *packet = (struct fast_ping_packet *)packet_data; struct icmp6_hdr *icmp6 = &packet->icmp6; struct cmsghdr *c = NULL; int hops = 0; if (data_len < (int)sizeof(struct icmp6_hdr)) { tlog(TLOG_DEBUG, "ping package length is invalid, %d, %d", data_len, (int)sizeof(struct fast_ping_packet)); return NULL; } for (c = CMSG_FIRSTHDR(msg); c; c = CMSG_NXTHDR(msg, c)) { if (c->cmsg_level != IPPROTO_IPV6) { continue; } switch (c->cmsg_type) { case IPV6_HOPLIMIT: #ifdef IPV6_2292HOPLIMIT case IPV6_2292HOPLIMIT: #endif if (c->cmsg_len < CMSG_LEN(sizeof(int))) { continue; } memcpy(&hops, CMSG_DATA(c), sizeof(hops)); break; default: break; } } packet->ttl = hops; if (icmp6->icmp6_type != ICMP6_ECHO_REPLY) { errno = ENETUNREACH; return NULL; } icmp_len = data_len; if (icmp_len < 16) { tlog(TLOG_ERROR, "length is invalid, %d", icmp_len); return NULL; } if (ping.no_unprivileged_ping) { if (icmp6->icmp6_id != ping.ident) { tlog(TLOG_ERROR, "ident failed, %d:%d", icmp6->icmp6_id, ping.ident); return NULL; } } return packet; } ================================================ FILE: src/fast_ping/ping_icmp6.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_ICMP6_H_ #define _FAST_PING_ICMP6_H_ #include "fast_ping.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ struct fast_ping_packet *_fast_ping_icmp6_packet(struct ping_host_struct *ping_host, struct msghdr *msg, u_char *packet_data, int data_len); void _fast_ping_install_filter_v6(int sock); int _fast_ping_sendping_v6(struct ping_host_struct *ping_host); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_ICMP6_H_ ================================================ FILE: src/fast_ping/ping_tcp.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/util.h" #include "notify_event.h" #include "ping_host.h" #include "ping_tcp.h" #include #include #include #include #include #include #include int _fast_ping_sendping_tcp(struct ping_host_struct *ping_host) { struct epoll_event event; int flags = 0; int fd = -1; int yes = 1; const int priority = SOCKET_PRIORITY; const int ip_tos = IP_TOS; _fast_ping_close_host_sock(ping_host); fd = socket(ping_host->ss_family, SOCK_STREAM, 0); if (fd < 0) { goto errout; } flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); set_sock_keepalive(fd, 0, 0, 0); /* Set the socket lingering so we will RST connections instead of wasting * bandwidth with the four-step close */ set_sock_lingertime(fd, 0); ping_host->seq++; if (connect(fd, &ping_host->addr, ping_host->addr_len) != 0) { if (errno != EINPROGRESS) { char ping_host_name[PING_MAX_HOSTLEN]; if (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL || errno == EHOSTUNREACH) { goto errout; } if (errno == EACCES || errno == EPERM) { if (bool_print_log == 0) { goto errout; } bool_print_log = 0; } tlog(TLOG_INFO, "connect %s, id %d, %s", get_host_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr), ping_host->sid, strerror(errno)); goto errout; } } gettimeofday(&ping_host->last, NULL); ping_host->fd = fd; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT | EPOLLERR; event.data.ptr = ping_host; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { ping_host->fd = -1; goto errout; } return 0; errout: if (fd > 0) { close(fd); ping_host->fd = -1; } return -1; } int _fast_ping_get_addr_by_tcp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type) { struct addrinfo *gai = NULL; int socktype = 0; FAST_PING_TYPE ping_type = 0; int sockproto = 0; char *service = NULL; char port_str[MAX_IP_LEN]; if (port <= 0) { port = 80; } sockproto = 0; socktype = SOCK_STREAM; snprintf(port_str, MAX_IP_LEN, "%d", port); service = port_str; ping_type = FAST_PING_TCP; gai = _fast_ping_getaddr(ip_str, service, socktype, sockproto); if (gai == NULL) { goto errout; } *out_gai = gai; *out_ping_type = ping_type; return 0; errout: if (gai) { freeaddrinfo(gai); } return -1; } int _fast_ping_process_tcp(struct ping_host_struct *ping_host, struct epoll_event *event, struct timeval *now) { struct timeval tvresult = *now; struct timeval *tvsend = &ping_host->last; int connect_error = 0; socklen_t len = sizeof(connect_error); if (event->events & EPOLLIN || event->events & EPOLLERR) { if (getsockopt(ping_host->fd, SOL_SOCKET, SO_ERROR, (char *)&connect_error, &len) != 0) { goto errout; } if (connect_error != 0 && connect_error != ECONNREFUSED) { goto errout; } } tv_sub(&tvresult, tvsend); if (ping_host->ping_callback) { _fast_ping_send_notify_event(ping_host, PING_RESULT_RESPONSE, ping_host->seq, ping_host->ttl, &tvresult); } ping_host->send = 0; _fast_ping_close_host_sock(ping_host); if (ping_host->count == 1) { _fast_ping_host_remove(ping_host); } return 0; errout: _fast_ping_host_remove(ping_host); return -1; } ================================================ FILE: src/fast_ping/ping_tcp.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_TCP_H_ #define _FAST_PING_TCP_H_ #include "fast_ping.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _fast_ping_process_tcp(struct ping_host_struct *ping_host, struct epoll_event *event, struct timeval *now); int _fast_ping_get_addr_by_tcp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type); int _fast_ping_sendping_tcp(struct ping_host_struct *ping_host); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_TCP_H_ ================================================ FILE: src/fast_ping/ping_tcp_syn.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/util.h" #include "notify_event.h" #include "ping_host.h" #include "ping_icmp.h" #include "ping_tcp_syn.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /* TCP/IP header structures */ struct pseudo_header { uint32_t source_address; uint32_t dest_address; uint8_t placeholder; uint8_t protocol; uint16_t tcp_length; } __attribute__((packed)); struct pseudo_header6 { struct in6_addr source_address; struct in6_addr dest_address; uint32_t tcp_length; uint8_t zeros[3]; uint8_t next_header; } __attribute__((packed)); /* Get local IP address based on destination address and routing table */ static int _tcp_syn_get_local_addr(int family, struct sockaddr *dest, struct sockaddr_storage *local) { int sock = -1; socklen_t addr_len; int ret = -1; sock = socket(family, SOCK_DGRAM, 0); if (sock < 0) { return -1; } addr_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); getsocket_inet(sock, (struct sockaddr *)local, &addr_len); /* Connect to determine which local interface will be used */ if (connect(sock, dest, addr_len) != 0) { goto cleanup; } if (getsocket_inet(sock, (struct sockaddr *)local, &addr_len) != 0) { goto cleanup; } ret = 0; cleanup: close(sock); return ret; } /* Reserve a TCP port by binding a SOCK_STREAM socket */ static int _tcp_syn_reserve_port(int family, int *out_fd, uint16_t *out_port, struct sockaddr_storage *out_addr) { int sock = -1; socklen_t addr_len; struct sockaddr_storage bind_addr; memset(&bind_addr, 0, sizeof(bind_addr)); sock = socket(family, SOCK_STREAM, 0); if (sock < 0) { tlog(TLOG_ERROR, "create TCP socket for port reservation failed, %s", strerror(errno)); return -1; } /* Bind to any address with port 0 (kernel will assign an available port) */ if (family == AF_INET) { struct sockaddr_in *addr_in = (struct sockaddr_in *)&bind_addr; addr_in->sin_family = AF_INET; addr_in->sin_addr.s_addr = INADDR_ANY; addr_in->sin_port = 0; addr_len = sizeof(struct sockaddr_in); } else if (family == AF_INET6) { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&bind_addr; addr_in6->sin6_family = AF_INET6; addr_in6->sin6_addr = in6addr_any; addr_in6->sin6_port = 0; addr_len = sizeof(struct sockaddr_in6); } else { close(sock); return -1; } if (bind(sock, (struct sockaddr *)&bind_addr, addr_len) < 0) { tlog(TLOG_ERROR, "bind socket for port reservation failed, %s", strerror(errno)); close(sock); return -1; } /* Retrieve the assigned port */ if (getsockname(sock, (struct sockaddr *)&bind_addr, &addr_len) != 0) { tlog(TLOG_ERROR, "getsockname failed, %s", strerror(errno)); close(sock); return -1; } /* Extract port number */ if (family == AF_INET) { *out_port = ntohs(((struct sockaddr_in *)&bind_addr)->sin_port); } else { *out_port = ntohs(((struct sockaddr_in6 *)&bind_addr)->sin6_port); } *out_fd = sock; memcpy(out_addr, &bind_addr, addr_len); return 0; } /* Install BPF filter for IPv4 to reduce unwanted TCP packets */ static int _tcp_syn_install_bpf_ipv4(int fd, uint16_t port) { /* Filter logic (same pattern as ICMP): * Accept packets where TCP destination port == our port * This filters out most unwanted TCP traffic */ struct sock_filter insns[] = { BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0), /* X = IP header length */ BPF_STMT(BPF_LD | BPF_H | BPF_IND, 2), /* A = TCP dst port at [X+2] */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, port, 0, 1), /* if (A == port) skip 0 else skip 1 */ BPF_STMT(BPF_RET | BPF_K, ~0U), /* accept: return -1 (pass) */ BPF_STMT(BPF_RET | BPF_K, 0) /* reject: return 0 (drop) */ }; struct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns}; if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) < 0) { tlog(TLOG_WARN, "WARNING: failed to install TCP SYN socket filter: %s", strerror(errno)); return -1; } return 0; } /* Install BPF filter for IPv6 to reduce unwanted TCP packets */ static int _tcp_syn_install_bpf_ipv6(int fd, uint16_t port) { /* Filter logic (same pattern as ICMP6): * Accept packets where TCP destination port == our port * IPv6 raw sockets don't include IP header, so use BPF_ABS mode */ struct sock_filter insns[] = { BPF_STMT(BPF_LD | BPF_H | BPF_ABS, 2), /* A = TCP dst port at offset 2 */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, port, 0, 1), /* if (A == port) skip 0 else skip 1 */ BPF_STMT(BPF_RET | BPF_K, ~0U), /* accept: return -1 (pass) */ BPF_STMT(BPF_RET | BPF_K, 0), /* reject: return 0 (drop) */ }; struct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns}; if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) < 0) { tlog(TLOG_WARN, "ERROR: failed to install TCP SYN IPv6 socket filter: %s", strerror(errno)); return -1; } return 0; } /* Build and send IPv4 RST packet */ static int _tcp_syn_send_rst_ipv4(struct ping_host_struct *ping_host, struct sockaddr_storage *local_addr, uint32_t seq, uint32_t ack_seq) { char packet[4096]; struct tcphdr *tcp_packet; int packet_len; char *pseudo_packet = NULL; int ret = -1; /* Verify address family */ if (local_addr->ss_family != AF_INET) { return -1; } struct iphdr *ip = (struct iphdr *)packet; struct sockaddr_in *local_in = (struct sockaddr_in *)local_addr; struct sockaddr_in *dest_in = (struct sockaddr_in *)&ping_host->addr; memset(packet, 0, sizeof(packet)); tcp_packet = (struct tcphdr *)(packet + sizeof(struct iphdr)); packet_len = sizeof(struct iphdr) + sizeof(struct tcphdr); /* RST packet should only contain TCP header, no data */ /* Fill IP header */ ip->ihl = 5; ip->version = 4; ip->tos = 0; ip->tot_len = htons(packet_len); ip->id = htons(ping_host->sid); ip->frag_off = 0; ip->ttl = 64; ip->protocol = IPPROTO_TCP; ip->check = 0; ip->saddr = local_in->sin_addr.s_addr; ip->daddr = dest_in->sin_addr.s_addr; ip->check = _fast_ping_checksum((uint16_t *)packet, sizeof(struct iphdr)); /* Fill TCP header */ tcp_packet->source = htons(ping_host->tcp_local_port); /* Use the same source port as SYN packet */ tcp_packet->dest = dest_in->sin_port; tcp_packet->seq = htonl(seq); tcp_packet->ack_seq = htonl(ack_seq); tcp_packet->doff = 5; tcp_packet->rst = 1; tcp_packet->ack = 1; tcp_packet->window = 0; tcp_packet->check = 0; tcp_packet->urg_ptr = 0; /* Calculate TCP checksum with pseudo header */ pseudo_packet = zalloc(1, sizeof(struct pseudo_header) + sizeof(struct tcphdr)); if (pseudo_packet == NULL) { goto errout; } struct pseudo_header *psh = (struct pseudo_header *)pseudo_packet; psh->source_address = local_in->sin_addr.s_addr; psh->dest_address = dest_in->sin_addr.s_addr; psh->placeholder = 0; psh->protocol = IPPROTO_TCP; psh->tcp_length = htons(sizeof(struct tcphdr)); memcpy(pseudo_packet + sizeof(struct pseudo_header), tcp_packet, sizeof(struct tcphdr)); tcp_packet->check = _fast_ping_checksum((uint16_t *)pseudo_packet, sizeof(struct pseudo_header) + sizeof(struct tcphdr)); if (sendto(ping.fd_tcp_syn, packet, packet_len, 0, &ping_host->addr, ping_host->addr_len) < 0) { tlog(TLOG_DEBUG, "send IPv4 RST packet failed, %s", strerror(errno)); goto errout; } ret = 0; errout: if (pseudo_packet) { free(pseudo_packet); } return ret; } /* Build and send IPv6 RST packet */ static int _tcp_syn_send_rst_ipv6(struct ping_host_struct *ping_host, struct sockaddr_storage *local_addr, uint32_t seq, uint32_t ack_seq) { char packet[4096]; struct tcphdr *tcp_packet; int packet_len; char *pseudo_packet = NULL; int ret = -1; /* Verify address family */ if (local_addr->ss_family != AF_INET6) { return -1; } struct sockaddr_in6 *local_in6 = (struct sockaddr_in6 *)local_addr; struct sockaddr_in6 *dest_in6 = (struct sockaddr_in6 *)&ping_host->addr; memset(packet, 0, sizeof(packet)); tcp_packet = (struct tcphdr *)packet; packet_len = sizeof(struct tcphdr); /* RST packet should only contain TCP header, no data */ /* Fill TCP header */ tcp_packet->source = htons(ping_host->tcp_local_port); /* Use the same source port as SYN packet */ tcp_packet->dest = dest_in6->sin6_port; tcp_packet->seq = htonl(seq); tcp_packet->ack_seq = htonl(ack_seq); tcp_packet->doff = 5; tcp_packet->rst = 1; tcp_packet->ack = 1; tcp_packet->window = 0; tcp_packet->check = 0; tcp_packet->urg_ptr = 0; /* Calculate TCP checksum with pseudo header */ pseudo_packet = malloc(sizeof(struct pseudo_header6) + sizeof(struct tcphdr)); if (pseudo_packet == NULL) { goto errout; } struct pseudo_header6 *psh6 = (struct pseudo_header6 *)pseudo_packet; memcpy(&psh6->source_address, &local_in6->sin6_addr, sizeof(struct in6_addr)); memcpy(&psh6->dest_address, &dest_in6->sin6_addr, sizeof(struct in6_addr)); psh6->tcp_length = htonl(sizeof(struct tcphdr)); memset(psh6->zeros, 0, 3); psh6->next_header = IPPROTO_TCP; memcpy(pseudo_packet + sizeof(struct pseudo_header6), tcp_packet, sizeof(struct tcphdr)); tcp_packet->check = _fast_ping_checksum((uint16_t *)pseudo_packet, sizeof(struct pseudo_header6) + sizeof(struct tcphdr)); /* For IPv6 raw TCP socket, sin6_port must be 0 */ struct sockaddr_storage dest_addr; memcpy(&dest_addr, &ping_host->addr, ping_host->addr_len); ((struct sockaddr_in6 *)&dest_addr)->sin6_port = 0; if (sendto(ping.fd_tcp_syn6, packet, packet_len, 0, (struct sockaddr *)&dest_addr, ping_host->addr_len) < 0) { tlog(TLOG_DEBUG, "send IPv6 RST packet failed, %s", strerror(errno)); goto errout; } ret = 0; errout: if (pseudo_packet) { free(pseudo_packet); } return ret; } /* Send RST packet to close connection */ __attribute__((unused)) static int _tcp_syn_send_rst(struct ping_host_struct *ping_host, uint32_t seq, uint32_t ack_seq) { struct sockaddr_storage local_addr; memset(&local_addr, 0, sizeof(local_addr)); if (_tcp_syn_get_local_addr(ping_host->ss_family, &ping_host->addr, &local_addr) != 0) { return -1; } if (ping_host->ss_family == AF_INET) { return _tcp_syn_send_rst_ipv4(ping_host, &local_addr, seq, ack_seq); } else if (ping_host->ss_family == AF_INET6) { return _tcp_syn_send_rst_ipv6(ping_host, &local_addr, seq, ack_seq); } return -1; } /* Build IPv4 SYN packet */ static int _tcp_syn_build_packet_ipv4(char *packet, struct ping_host_struct *ping_host, struct sockaddr_storage *local_addr, uint16_t local_port, uint32_t seq_num) { struct iphdr *ip = (struct iphdr *)packet; struct tcphdr *tcph; struct sockaddr_in *local_in = (struct sockaddr_in *)local_addr; struct sockaddr_in *dest_in = (struct sockaddr_in *)&ping_host->addr; char *pseudo_packet = NULL; int packet_len; int ret = -1; tcph = (struct tcphdr *)(packet + sizeof(struct iphdr)); packet_len = sizeof(struct iphdr) + sizeof(struct tcphdr); /* Fill IP header */ ip->ihl = 5; ip->version = 4; ip->tos = 0; ip->tot_len = htons(packet_len); ip->id = htons(ping_host->sid); ip->frag_off = 0; ip->ttl = 64; ip->protocol = IPPROTO_TCP; ip->check = 0; ip->saddr = local_in->sin_addr.s_addr; ip->daddr = dest_in->sin_addr.s_addr; ip->check = _fast_ping_checksum((uint16_t *)packet, sizeof(struct iphdr)); /* Fill TCP header - use reserved port */ memset(tcph, 0, sizeof(struct tcphdr)); tcph->source = htons(local_port); tcph->dest = dest_in->sin_port; tcph->seq = htonl(seq_num); tcph->ack_seq = htonl(0); tcph->doff = 5; tcph->fin = 0; tcph->syn = 1; tcph->rst = 0; tcph->psh = 0; tcph->ack = 0; tcph->urg = 0; tcph->window = htons(8192); tcph->check = 0; tcph->urg_ptr = htons(0); /* Calculate TCP checksum with pseudo header */ pseudo_packet = zalloc(1, sizeof(struct pseudo_header) + sizeof(struct tcphdr)); if (pseudo_packet == NULL) { goto errout; } struct pseudo_header *psh = (struct pseudo_header *)pseudo_packet; psh->source_address = local_in->sin_addr.s_addr; psh->dest_address = dest_in->sin_addr.s_addr; psh->placeholder = 0; psh->protocol = IPPROTO_TCP; psh->tcp_length = htons(sizeof(struct tcphdr)); tcph->check = 0; memcpy(pseudo_packet + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr)); tcph->check = _fast_ping_checksum((uint16_t *)pseudo_packet, sizeof(struct pseudo_header) + sizeof(struct tcphdr)); ret = packet_len; errout: if (pseudo_packet) { free(pseudo_packet); } return ret; } /* Build IPv6 SYN packet */ static int _tcp_syn_build_packet_ipv6(char *packet, struct ping_host_struct *ping_host, struct sockaddr_storage *local_addr, uint16_t local_port, uint32_t seq_num) { struct tcphdr *tcph; struct sockaddr_in6 *local_in6 = (struct sockaddr_in6 *)local_addr; struct sockaddr_in6 *dest_in6 = (struct sockaddr_in6 *)&ping_host->addr; char *pseudo_packet = NULL; int packet_len; int ret = -1; tcph = (struct tcphdr *)packet; packet_len = sizeof(struct tcphdr); memset(tcph, 0, sizeof(struct tcphdr)); tcph->source = htons(local_port); tcph->dest = dest_in6->sin6_port; tcph->seq = htonl(seq_num); tcph->ack_seq = 0; tcph->doff = 5; /* TCP header length: 5 * 4 = 20 bytes */ tcph->syn = 1; tcph->window = htons(8192); tcph->check = 0; tcph->urg_ptr = 0; /* Calculate TCP checksum with pseudo header */ pseudo_packet = malloc(sizeof(struct pseudo_header6) + sizeof(struct tcphdr)); if (pseudo_packet == NULL) { goto errout; } struct pseudo_header6 *psh6 = (struct pseudo_header6 *)pseudo_packet; memcpy(&psh6->source_address, &local_in6->sin6_addr, sizeof(struct in6_addr)); memcpy(&psh6->dest_address, &dest_in6->sin6_addr, sizeof(struct in6_addr)); psh6->tcp_length = htonl(sizeof(struct tcphdr)); memset(psh6->zeros, 0, 3); psh6->next_header = IPPROTO_TCP; memcpy(pseudo_packet + sizeof(struct pseudo_header6), tcph, sizeof(struct tcphdr)); tcph->check = _fast_ping_checksum((uint16_t *)pseudo_packet, sizeof(struct pseudo_header6) + sizeof(struct tcphdr)); ret = packet_len; errout: if (pseudo_packet) { free(pseudo_packet); } return ret; } int _fast_ping_sendping_tcp_syn(struct ping_host_struct *ping_host) { char packet[4096]; int packet_len = 0; int fd = -1; struct sockaddr_storage local_addr; uint32_t seq_num = 0; uint16_t local_port = 0; /* Create socket on first use */ if (_fast_ping_tcp_syn_create_socket(ping_host) < 0) { return -1; } memset(&local_addr, 0, sizeof(local_addr)); if (_tcp_syn_get_local_addr(ping_host->ss_family, &ping_host->addr, &local_addr) != 0) { return -1; } /* Use the reserved port instead of getting a new one each time */ if (ping_host->ss_family == AF_INET) { if (ping.tcp_syn_bind_port == 0) { tlog(TLOG_ERROR, "TCP SYN bind port not initialized"); return -1; } local_port = ping.tcp_syn_bind_port; ping_host->tcp_local_port = local_port; fd = ping.fd_tcp_syn; } else if (ping_host->ss_family == AF_INET6) { if (ping.tcp_syn6_bind_port == 0) { tlog(TLOG_ERROR, "TCP SYN IPv6 bind port not initialized"); return -1; } local_port = ping.tcp_syn6_bind_port; ping_host->tcp_local_port = local_port; fd = ping.fd_tcp_syn6; } else { return -1; } memset(packet, 0, sizeof(packet)); /* Generate sequence number: combine sid and timestamp for uniqueness */ seq_num = (ping_host->sid << 16) | (get_tick_count() & 0xFFFF); ping_host->seq++; /* Build packet based on address family */ if (ping_host->ss_family == AF_INET) { packet_len = _tcp_syn_build_packet_ipv4(packet, ping_host, &local_addr, local_port, seq_num); } else if (ping_host->ss_family == AF_INET6) { packet_len = _tcp_syn_build_packet_ipv6(packet, ping_host, &local_addr, local_port, seq_num); } else { return -1; } if (packet_len < 0 || fd < 0) { return -1; } /* Send SYN packet */ gettimeofday(&ping_host->last, NULL); struct sockaddr_storage dest_addr; memcpy(&dest_addr, &ping_host->addr, ping_host->addr_len); if (ping_host->ss_family == AF_INET6) { struct sockaddr_in6 *dest_in6 = (struct sockaddr_in6 *)&dest_addr; dest_in6->sin6_port = 0; /* Must be 0 for raw TCP socket */ } ssize_t len = sendto(fd, packet, packet_len, 0, (struct sockaddr *)&dest_addr, ping_host->addr_len); if (len < 0) { tlog(TLOG_DEBUG, "send SYN packet failed, %s", strerror(errno)); return -1; } return 0; } int _fast_ping_get_addr_by_tcp_syn(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type) { struct addrinfo *gai = NULL; char port_str[MAX_IP_LEN]; if (port <= 0) { port = 80; } snprintf(port_str, sizeof(port_str), "%d", port); gai = _fast_ping_getaddr(ip_str, port_str, SOCK_STREAM, 0); if (gai == NULL) { return -1; } *out_gai = gai; *out_ping_type = FAST_PING_TCP_SYN; return 0; } int _fast_ping_process_tcp_syn(struct ping_host_struct *ping_host, struct timeval *now) { char packet[4096]; struct sockaddr_storage from_addr; socklen_t from_len = sizeof(from_addr); int fd = -1; ssize_t recv_len = 0; struct ping_host_struct *recv_ping_host = NULL; /* Determine which socket received data */ if (ping_host->type == FAST_PING_TCP_SYN && ping_host->fd == ping.fd_tcp_syn) { fd = ping.fd_tcp_syn; } else if (ping_host->type == FAST_PING_TCP_SYN && ping_host->fd == ping.fd_tcp_syn6) { fd = ping.fd_tcp_syn6; } else { return 0; } if (fd < 0) { return -1; } /* Receive all available packets */ while (1) { memset(packet, 0, sizeof(packet)); from_len = sizeof(from_addr); recv_len = recvfrom(fd, packet, sizeof(packet), MSG_DONTWAIT, (struct sockaddr *)&from_addr, &from_len); if (recv_len < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } tlog(TLOG_DEBUG, "recvfrom error: %s", strerror(errno)); return -1; } /* Parse packet based on socket type */ struct tcphdr *tcp = NULL; /* For IPv4 raw socket, packet includes IP header */ if (fd == ping.fd_tcp_syn) { if (recv_len < (ssize_t)(sizeof(struct iphdr) + sizeof(struct tcphdr))) { continue; } struct iphdr *ip = (struct iphdr *)packet; /* Validate IP header length */ if (ip->ihl < 5 || ip->ihl > 15) { continue; } /* Ensure we have enough data for the full IP header */ size_t ip_header_len = ip->ihl * 4; if (recv_len < (ssize_t)(ip_header_len + sizeof(struct tcphdr))) { continue; } tcp = (struct tcphdr *)(packet + ip_header_len); /* Extract source address from IP header */ struct sockaddr_in *from_in = (struct sockaddr_in *)&from_addr; from_in->sin_family = AF_INET; from_in->sin_addr.s_addr = ip->saddr; } else if (fd == ping.fd_tcp_syn6) { /* For IPv6 raw socket, packet starts with TCP header */ if (recv_len < (ssize_t)(sizeof(struct tcphdr))) { continue; } tcp = (struct tcphdr *)packet; } else { continue; } /* Validate TCP header data offset */ if (tcp->doff < 5 || tcp->doff > 15) { continue; } /* Ensure we have enough data for the full TCP header */ size_t tcp_header_len = tcp->doff * 4; if (fd == ping.fd_tcp_syn) { /* For IPv4, account for IP header */ size_t ip_header_len = ((struct iphdr *)packet)->ihl * 4; if (recv_len < (ssize_t)(ip_header_len + tcp_header_len)) { continue; } } else { /* For IPv6 */ if (recv_len < (ssize_t)tcp_header_len) { continue; } } /* Check if SYN-ACK or RST */ if (!((tcp->syn && tcp->ack) || (tcp->rst && tcp->ack))) { continue; } /* Extract sid from ack_seq * We sent: seq = (sid << 16) | timestamp * Server responds: ack_seq = our_seq + 1 * So: (ack_seq - 1) >> 16 should equal our sid */ uint32_t received_ack = ntohl(tcp->ack_seq); uint16_t received_sid = (received_ack - 1) >> 16; /* Calculate hash key using sid and source address */ uint32_t addrkey = _fast_ping_hash_key(received_sid, (struct sockaddr *)&from_addr); /* Find matching ping_host using hash table lookup */ pthread_mutex_lock(&ping.map_lock); recv_ping_host = NULL; hash_for_each_possible(ping.addrmap, recv_ping_host, addr_node, addrkey) { if (recv_ping_host->type != FAST_PING_TCP_SYN) { continue; } /* Verify sid matches */ if (recv_ping_host->sid != received_sid) { continue; } /* Verify address matches */ if (_fast_ping_sockaddr_ip_cmp(&recv_ping_host->addr, recv_ping_host->addr_len, (struct sockaddr *)&from_addr, from_len) != 0) { continue; } /* Check if currently sending - skip old entries */ if (recv_ping_host->send == 0) { continue; } /* Found matching ping_host */ _fast_ping_host_get(recv_ping_host); break; } pthread_mutex_unlock(&ping.map_lock); if (recv_ping_host == NULL) { continue; } /* No need to send RST, the linux kernel will send RST automatically for raw TCP sockets */ /* Calculate RTT */ struct timeval tvresult = *now; struct timeval *tvsend = &recv_ping_host->last; tv_sub(&tvresult, tvsend); /* Report success */ _fast_ping_send_notify_event(recv_ping_host, PING_RESULT_RESPONSE, recv_ping_host->seq, 64, &tvresult); recv_ping_host->send = 0; if (recv_ping_host->count == 1) { /* Remove this ping_host */ _fast_ping_host_remove(recv_ping_host); } _fast_ping_host_put(recv_ping_host); } return 0; } /* Create IPv4 TCP SYN raw socket with port reservation and BPF filter */ static int _fast_ping_create_tcp_syn_sock_ipv4(void) { int fd = -1; const int on = 1; uint16_t bind_port = 0; /* Reserve a port for IPv4 TCP SYN ping */ if (ping.fd_tcp_syn_bind <= 0) { if (_tcp_syn_reserve_port(AF_INET, &ping.fd_tcp_syn_bind, &ping.tcp_syn_bind_port, (struct sockaddr_storage *)&ping.tcp_syn_bind_addr) != 0) { tlog(TLOG_ERROR, "failed to reserve IPv4 port for TCP SYN"); goto errout; } } bind_port = ping.tcp_syn_bind_port; /* Create IPv4 raw socket */ fd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); if (fd < 0) { if (errno == EPERM || errno == EACCES) { tlog(TLOG_DEBUG, "create TCP SYN raw socket failed, %s (need root or CAP_NET_RAW capability)", strerror(errno)); } else { tlog(TLOG_ERROR, "create TCP SYN raw socket failed, %s", strerror(errno)); } goto errout; } /* Set socket options */ if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) { tlog(TLOG_ERROR, "setsockopt IP_HDRINCL failed, %s", strerror(errno)); goto errout; } /* Install BPF filter */ _tcp_syn_install_bpf_ipv4(fd, bind_port); /* Set non-blocking */ if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) { tlog(TLOG_ERROR, "fcntl set non-blocking failed, %s", strerror(errno)); goto errout; } return fd; errout: if (fd > 0) { close(fd); } return -1; } /* Create IPv6 TCP SYN raw socket with port reservation and BPF filter */ static int _fast_ping_create_tcp_syn_sock_ipv6(void) { int fd = -1; uint16_t bind_port = 0; /* Reserve a port for IPv6 TCP SYN ping */ if (ping.fd_tcp_syn6_bind <= 0) { if (_tcp_syn_reserve_port(AF_INET6, &ping.fd_tcp_syn6_bind, &ping.tcp_syn6_bind_port, (struct sockaddr_storage *)&ping.tcp_syn6_bind_addr) != 0) { tlog(TLOG_ERROR, "failed to reserve IPv6 port for TCP SYN"); goto errout; } } bind_port = ping.tcp_syn6_bind_port; /* Create IPv6 raw socket */ fd = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP); if (fd < 0) { if (errno != EAFNOSUPPORT) { tlog(TLOG_DEBUG, "create TCP SYN IPv6 raw socket failed, %s", strerror(errno)); } goto errout; } /* Install BPF filter */ _tcp_syn_install_bpf_ipv6(fd, bind_port); /* Set non-blocking */ if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) { tlog(TLOG_ERROR, "fcntl set non-blocking failed, %s", strerror(errno)); goto errout; } return fd; errout: if (fd > 0) { close(fd); } return -1; } static int _fast_ping_create_tcp_syn_sock(int is_ipv6) { int fd = -1; struct epoll_event event; struct ping_host_struct *tcp_syn_host = NULL; if (!is_ipv6) { fd = _fast_ping_create_tcp_syn_sock_ipv4(); if (fd < 0) { goto errout; } tcp_syn_host = &ping.tcp_syn_host; } else { fd = _fast_ping_create_tcp_syn_sock_ipv6(); if (fd < 0) { goto errout; } tcp_syn_host = &ping.tcp_syn6_host; } tcp_syn_host->fd = fd; tcp_syn_host->type = FAST_PING_TCP_SYN; /* Add to epoll */ memset(&event, 0, sizeof(event)); event.events = EPOLLIN; event.data.ptr = tcp_syn_host; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { tlog(TLOG_ERROR, "add TCP SYN socket to epoll failed, %s", strerror(errno)); goto errout; } return fd; errout: if (fd > 0) { close(fd); } if (tcp_syn_host) { tcp_syn_host->fd = -1; tcp_syn_host->type = 0; } return -1; } static int _fast_ping_create_tcp_syn(int is_ipv6) { int fd = -1; int *set_fd = NULL; pthread_mutex_lock(&ping.lock); if (!is_ipv6) { set_fd = &ping.fd_tcp_syn; } else { set_fd = &ping.fd_tcp_syn6; } if (*set_fd > 0) { goto out; } fd = _fast_ping_create_tcp_syn_sock(is_ipv6); if (fd < 0) { goto errout; } *set_fd = fd; out: pthread_mutex_unlock(&ping.lock); return *set_fd; errout: if (fd > 0) { close(fd); } pthread_mutex_unlock(&ping.lock); return -1; } int _fast_ping_tcp_syn_create_socket(struct ping_host_struct *ping_host) { FAST_PING_TYPE type = ping_host->type; if (type != FAST_PING_TCP_SYN) { goto errout; } /* Determine IPv4 or IPv6 based on address family */ if (ping_host->ss_family == AF_INET) { if (_fast_ping_create_tcp_syn(0) < 0) { goto errout; } if (ping.fd_tcp_syn <= 0) { errno = EADDRNOTAVAIL; goto errout; } } else if (ping_host->ss_family == AF_INET6) { /* For IPv6, we need to create a separate socket */ /* Use a special internal type indicator */ pthread_mutex_lock(&ping.lock); if (ping.fd_tcp_syn6 <= 0) { int fd = _fast_ping_create_tcp_syn_sock(1); if (fd > 0) { ping.fd_tcp_syn6 = fd; } } pthread_mutex_unlock(&ping.lock); if (ping.fd_tcp_syn6 <= 0) { errno = EADDRNOTAVAIL; goto errout; } } else { goto errout; } return 0; errout: return -1; } void _fast_ping_close_tcp_syn(void) { if (ping.fd_tcp_syn > 0) { close(ping.fd_tcp_syn); ping.fd_tcp_syn = -1; } if (ping.fd_tcp_syn6 > 0) { close(ping.fd_tcp_syn6); ping.fd_tcp_syn6 = -1; } /* Close bind sockets */ if (ping.fd_tcp_syn_bind > 0) { close(ping.fd_tcp_syn_bind); ping.fd_tcp_syn_bind = -1; ping.tcp_syn_bind_port = 0; } if (ping.fd_tcp_syn6_bind > 0) { close(ping.fd_tcp_syn6_bind); ping.fd_tcp_syn6_bind = -1; ping.tcp_syn6_bind_port = 0; } } ================================================ FILE: src/fast_ping/ping_tcp_syn.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_TCP_SYN_H_ #define _FAST_PING_TCP_SYN_H_ #include "fast_ping.h" #include #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _fast_ping_process_tcp_syn(struct ping_host_struct *ping_host, struct timeval *now); int _fast_ping_get_addr_by_tcp_syn(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type); int _fast_ping_sendping_tcp_syn(struct ping_host_struct *ping_host); int _fast_ping_tcp_syn_create_socket(struct ping_host_struct *ping_host); void _fast_ping_close_tcp_syn(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_TCP_SYN_H_ ================================================ FILE: src/fast_ping/ping_udp.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/util.h" #include "notify_event.h" #include "ping_host.h" #include "ping_icmp.h" #include "ping_udp.h" #include #include #include #include #include int _fast_ping_sendping_udp(struct ping_host_struct *ping_host) { struct ping_dns_head dns_head; int len = 0; int flag = 0; int fd = -1; flag |= (0 << 15) & 0x8000; flag |= (2 << 11) & 0x7800; flag |= (0 << 10) & 0x0400; flag |= (0 << 9) & 0x0200; flag |= (0 << 8) & 0x0100; flag |= (0 << 7) & 0x0080; flag |= (0 << 0) & 0x000F; if (ping_host->type == FAST_PING_UDP) { fd = ping.fd_udp; } else if (ping_host->type == FAST_PING_UDP6) { fd = ping.fd_udp6; } else { return -1; } ping_host->seq++; memset(&dns_head, 0, sizeof(dns_head)); dns_head.id = htons(ping_host->sid); dns_head.flag = flag; dns_head.qdcount = htons(1); dns_head.qd_name = 0; dns_head.q_qtype = htons(2); /* DNS_T_NS */ dns_head.q_qclass = htons(1); gettimeofday(&ping_host->last, NULL); len = sendto(fd, &dns_head, sizeof(dns_head), 0, &ping_host->addr, ping_host->addr_len); if (len != sizeof(dns_head)) { int err = errno; if (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL || errno == EPERM || errno == EACCES) { goto errout; } char ping_host_name[PING_MAX_HOSTLEN]; tlog(TLOG_ERROR, "sendto %s, id %d, %s", get_host_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr), ping_host->sid, strerror(err)); goto errout; } return 0; errout: return -1; } static int _fast_ping_create_udp_sock(FAST_PING_TYPE type) { int fd = -1; struct ping_host_struct *udp_host = NULL; struct epoll_event event; const int val = 255; const int on = 1; const int ip_tos = (IPTOS_LOWDELAY | IPTOS_RELIABILITY); switch (type) { case FAST_PING_UDP: fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { tlog(TLOG_ERROR, "create udp socket failed, %s\n", strerror(errno)); goto errout; } udp_host = &ping.udp_host; udp_host->type = FAST_PING_UDP; break; case FAST_PING_UDP6: fd = socket(AF_INET6, SOCK_DGRAM, 0); if (fd < 0) { tlog(TLOG_ERROR, "create udp socket failed, %s\n", strerror(errno)); goto errout; } udp_host = &ping.udp6_host; udp_host->type = FAST_PING_UDP6; setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)); setsockopt(fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on)); setsockopt(fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on)); break; default: return -1; } setsockopt(fd, SOL_IP, IP_TTL, &val, sizeof(val)); setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on)); setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); memset(&event, 0, sizeof(event)); event.events = EPOLLIN; event.data.ptr = udp_host; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { goto errout; } udp_host->fd = fd; return fd; errout: close(fd); return -1; } static int _fast_ping_create_udp(FAST_PING_TYPE type) { int fd = -1; int *set_fd = NULL; pthread_mutex_lock(&ping.lock); switch (type) { case FAST_PING_UDP: set_fd = &ping.fd_udp; break; case FAST_PING_UDP6: set_fd = &ping.fd_udp6; break; default: goto errout; break; } if (*set_fd > 0) { goto out; } fd = _fast_ping_create_udp_sock(type); if (fd < 0) { goto errout; } *set_fd = fd; out: pthread_mutex_unlock(&ping.lock); return *set_fd; errout: if (fd > 0) { close(fd); } pthread_mutex_unlock(&ping.lock); return -1; } int _fast_ping_get_addr_by_dns(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type) { struct addrinfo *gai = NULL; int socktype = 0; FAST_PING_TYPE ping_type = 0; int sockproto = 0; char port_str[MAX_IP_LEN]; int domain = -1; char *service = NULL; if (port <= 0) { port = 53; } domain = _fast_ping_getdomain(ip_str); if (domain < 0) { goto errout; } switch (domain) { case AF_INET: ping_type = FAST_PING_UDP; break; case AF_INET6: ping_type = FAST_PING_UDP6; break; default: goto errout; break; } sockproto = 0; socktype = SOCK_DGRAM; snprintf(port_str, MAX_IP_LEN, "%d", port); service = port_str; if (_fast_ping_create_udp(ping_type) < 0) { goto errout; } gai = _fast_ping_getaddr(ip_str, service, socktype, sockproto); if (gai == NULL) { goto errout; } *out_gai = gai; *out_ping_type = ping_type; return 0; errout: if (gai) { freeaddrinfo(gai); } return -1; } int _fast_ping_process_udp(struct ping_host_struct *ping_host, struct timeval *now) { ssize_t len = 0; u_char inpacket[ICMP_INPACKET_SIZE]; struct sockaddr_storage from; struct ping_host_struct *recv_ping_host = NULL; struct ping_dns_head *dns_head = NULL; socklen_t from_len = sizeof(from); uint32_t addrkey = 0; struct timeval tvresult = *now; struct timeval *tvsend = NULL; unsigned int sid = 0; struct msghdr msg; struct iovec iov; char ans_data[4096]; struct cmsghdr *cmsg = NULL; int ttl = 0; memset(&msg, 0, sizeof(msg)); iov.iov_base = (char *)inpacket; iov.iov_len = sizeof(inpacket); msg.msg_name = &from; msg.msg_namelen = sizeof(from); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = ans_data; msg.msg_controllen = sizeof(ans_data); len = recvmsg(ping_host->fd, &msg, MSG_DONTWAIT); if (len < 0) { tlog(TLOG_ERROR, "recvfrom failed, %s\n", strerror(errno)); goto errout; } for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_TTL) { if (cmsg->cmsg_len >= sizeof(int)) { int *ttlPtr = (int *)CMSG_DATA(cmsg); ttl = *ttlPtr; } } else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_HOPLIMIT) { if (cmsg->cmsg_len >= sizeof(int)) { int *ttlPtr = (int *)CMSG_DATA(cmsg); ttl = *ttlPtr; } } } from_len = msg.msg_namelen; dns_head = (struct ping_dns_head *)inpacket; if (len < (ssize_t)sizeof(*dns_head)) { goto errout; } sid = ntohs(dns_head->id); addrkey = _fast_ping_hash_key(sid, (struct sockaddr *)&from); pthread_mutex_lock(&ping.map_lock); hash_for_each_possible(ping.addrmap, recv_ping_host, addr_node, addrkey) { if (_fast_ping_sockaddr_ip_cmp(&recv_ping_host->addr, recv_ping_host->addr_len, (struct sockaddr *)&from, from_len) == 0 && recv_ping_host->sid == sid) { _fast_ping_host_get(recv_ping_host); break; } } pthread_mutex_unlock(&ping.map_lock); if (recv_ping_host == NULL) { return -1; } recv_ping_host->ttl = ttl; tvsend = &recv_ping_host->last; tv_sub(&tvresult, tvsend); if (recv_ping_host->ping_callback) { _fast_ping_send_notify_event(recv_ping_host, PING_RESULT_RESPONSE, recv_ping_host->seq, recv_ping_host->ttl, &tvresult); } recv_ping_host->send = 0; if (recv_ping_host->count == 1) { _fast_ping_host_remove(recv_ping_host); } _fast_ping_host_put(recv_ping_host); return 0; errout: return -1; } void _fast_ping_close_udp(void) { if (ping.fd_udp > 0) { epoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping.fd_udp, NULL); close(ping.fd_udp); ping.fd_udp = -1; } if (ping.fd_udp6 > 0) { epoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping.fd_udp6, NULL); close(ping.fd_udp6); ping.fd_udp6 = -1; } } ================================================ FILE: src/fast_ping/ping_udp.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_UDP_H_ #define _FAST_PING_UDP_H_ #include "fast_ping.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int _fast_ping_get_addr_by_dns(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type); int _fast_ping_sendping_udp(struct ping_host_struct *ping_host); int _fast_ping_process_udp(struct ping_host_struct *ping_host, struct timeval *now); void _fast_ping_close_udp(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_UDP_H_ ================================================ FILE: src/fast_ping/wakeup_event.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "wakeup_event.h" #include #include #include #include void _fast_ping_wakeup_thread(void) { uint64_t u = 1; int unused __attribute__((unused)); unused = write(ping.event_fd, &u, sizeof(u)); } int _fast_ping_init_wakeup_event(void) { int fdevent = -1; fdevent = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (fdevent < 0) { tlog(TLOG_ERROR, "create eventfd failed, %s\n", strerror(errno)); goto errout; } struct epoll_event event; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLERR; event.data.ptr = NULL; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fdevent, &event) != 0) { tlog(TLOG_ERROR, "set eventfd failed, %s\n", strerror(errno)); goto errout; } ping.event_fd = fdevent; return 0; errout: return -1; } ================================================ FILE: src/fast_ping/wakeup_event.h ================================================ /************************************************************************* * * 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 . */ #ifndef _FAST_PING_WAKEUP_EVENT_H_ #define _FAST_PING_WAKEUP_EVENT_H_ #include "fast_ping.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ void _fast_ping_wakeup_thread(void); int _fast_ping_init_wakeup_event(void); #ifdef __cplusplus } #endif /*__cplusplus */ #endif // !_FAST_PING_WAKEUP_EVENT_H_ ================================================ FILE: src/http_parse/hpack.c ================================================ #include "hpack.h" #include #include #include /* HPACK static table (RFC 7541 Appendix A) */ struct hpack_static_entry { const char *name; const char *value; }; /* clang-format off */ static const struct hpack_static_entry hpack_static_table[] = { {":authority", ""}, {":method", "GET"}, {":method", "POST"}, {":path", "/"}, {":path", "/index.html"}, {":scheme", "http"}, {":scheme", "https"}, {":status", "200"}, {":status", "204"}, {":status", "206"}, {":status", "304"}, {":status", "400"}, {":status", "404"}, {":status", "500"}, {"accept-charset", ""}, {"accept-encoding", "gzip, deflate"}, {"accept-language", ""}, {"accept-ranges", ""}, {"accept", ""}, {"access-control-allow-origin", ""}, {"age", ""}, {"allow", ""}, {"authorization", ""}, {"cache-control", ""}, {"content-disposition", ""}, {"content-encoding", ""}, {"content-language", ""}, {"content-length", ""}, {"content-location", ""}, {"content-range", ""}, {"content-type", ""}, {"cookie", ""}, {"date", ""}, {"etag", ""}, {"expect", ""}, {"expires", ""}, {"from", ""}, {"host", ""}, {"if-match", ""}, {"if-modified-since", ""}, {"if-none-match", ""}, {"if-range", ""}, {"if-unmodified-since", ""}, {"last-modified", ""}, {"link", ""}, {"location", ""}, {"max-forwards", ""}, {"proxy-authenticate", ""}, {"proxy-authorization", ""}, {"range", ""}, {"referer", ""}, {"refresh", ""}, {"retry-after", ""}, {"server", ""}, {"set-cookie", ""}, {"strict-transport-security", ""}, {"transfer-encoding", ""}, {"user-agent", ""}, {"vary", ""}, {"via", ""}, {"www-authenticate", ""} }; /* clang-format on */ #define HPACK_STATIC_TABLE_SIZE (sizeof(hpack_static_table) / sizeof(hpack_static_table[0])) /* HPACK integer encoding/decoding */ static int hpack_encode_integer(uint64_t value, int prefix_bits, uint8_t *buf, int buf_size) { int max_prefix = (1 << prefix_bits) - 1; int offset = 0; if (value < (uint64_t)max_prefix) { if (buf_size < 1) { return -1; } buf[0] |= (uint8_t)value; return 1; } if (buf_size < 1) { return -1; } buf[offset++] |= (uint8_t)max_prefix; value -= max_prefix; while (value >= 128) { if (offset >= buf_size) { return -1; } buf[offset++] = (uint8_t)((value & 0x7F) | 0x80); value >>= 7; } if (offset >= buf_size) { return -1; } buf[offset++] = (uint8_t)value; return offset; } static int hpack_decode_integer(const uint8_t *data, int data_len, int prefix_bits, uint64_t *value) { int max_prefix = (1 << prefix_bits) - 1; int offset = 0; uint64_t result; int shift = 0; if (data_len < 1) { return -1; } result = data[offset++] & max_prefix; if (result < (uint64_t)max_prefix) { *value = result; return offset; } while (offset < data_len) { uint8_t byte = data[offset++]; result += (uint64_t)(byte & 0x7F) << shift; shift += 7; if ((byte & 0x80) == 0) { *value = result; return offset; } if (shift > 63) { return -1; } } return -1; } /* HPACK string encoding/decoding */ static int hpack_encode_string(const char *str, uint8_t *buf, int buf_size) { int len = strlen(str); int offset = 0; int ret; if (buf_size < 1) { return -1; } buf[offset] = 0; /* No Huffman encoding */ ret = hpack_encode_integer(len, 7, buf + offset, buf_size - offset); if (ret < 0) { return -1; } offset += ret; if (offset + len > buf_size) { return -1; } memcpy(buf + offset, str, len); offset += len; return offset; } /* HPACK Huffman decoding table based on RFC 7541 Appendix B */ /* Each entry contains: symbol, code length in bits */ struct huffman_decode_entry { uint32_t bits; /* Huffman code bits */ uint8_t nbits; /* Number of bits in code */ uint8_t symbol; /* Decoded symbol */ }; /* Complete Huffman decoding table for HPACK (RFC 7541 Appendix B) */ /* Sorted by code for binary search */ static const struct huffman_decode_entry huffman_table[] = { /* 5-bit codes */ {0x00, 5, '0'}, {0x01, 5, '1'}, {0x02, 5, '2'}, {0x03, 5, 'a'}, {0x04, 5, 'c'}, {0x05, 5, 'e'}, {0x06, 5, 'i'}, {0x07, 5, 'o'}, {0x08, 5, 's'}, {0x09, 5, 't'}, /* 6-bit codes */ {0x14, 6, ' '}, {0x15, 6, '%'}, {0x16, 6, '-'}, {0x17, 6, '.'}, {0x18, 6, '/'}, {0x19, 6, '3'}, {0x1a, 6, '4'}, {0x1b, 6, '5'}, {0x1c, 6, '6'}, {0x1d, 6, '7'}, {0x1e, 6, '8'}, {0x1f, 6, '9'}, {0x20, 6, '='}, {0x21, 6, 'A'}, {0x22, 6, '_'}, {0x23, 6, 'b'}, {0x24, 6, 'd'}, {0x25, 6, 'f'}, {0x26, 6, 'g'}, {0x27, 6, 'h'}, {0x28, 6, 'l'}, {0x29, 6, 'm'}, {0x2a, 6, 'n'}, {0x2b, 6, 'p'}, {0x2c, 6, 'r'}, {0x2d, 6, 'u'}, /* 7-bit codes */ {0x5c, 7, ':'}, {0x5d, 7, 'B'}, {0x5e, 7, 'C'}, {0x5f, 7, 'D'}, {0x60, 7, 'E'}, {0x61, 7, 'F'}, {0x62, 7, 'G'}, {0x63, 7, 'H'}, {0x64, 7, 'I'}, {0x65, 7, 'J'}, {0x66, 7, 'K'}, {0x67, 7, 'L'}, {0x68, 7, 'M'}, {0x69, 7, 'N'}, {0x6a, 7, 'O'}, {0x6b, 7, 'P'}, {0x6c, 7, 'Q'}, {0x6d, 7, 'R'}, {0x6e, 7, 'S'}, {0x6f, 7, 'T'}, {0x70, 7, 'U'}, {0x71, 7, 'V'}, {0x72, 7, 'W'}, {0x73, 7, 'Y'}, {0x74, 7, 'j'}, {0x75, 7, 'k'}, {0x76, 7, 'q'}, {0x77, 7, 'v'}, {0x78, 7, 'w'}, {0x79, 7, 'x'}, {0x7a, 7, 'y'}, {0x7b, 7, 'z'}, /* 8-bit codes */ {0xf8, 8, '&'}, {0xf9, 8, '*'}, {0xfa, 8, ','}, {0xfb, 8, ';'}, {0xfc, 8, 'X'}, {0xfd, 8, 'Z'}, /* 10-bit codes */ {0x3f8, 10, '!'}, {0x3f9, 10, '"'}, {0x3fa, 10, '('}, {0x3fb, 10, ')'}, {0x3fc, 10, '?'}, /* 11-bit codes */ {0x7fa, 11, '#'}, {0x7fb, 11, '>'}, /* 12-bit codes */ {0xffa, 12, '$'}, {0xffb, 12, '@'}, {0xffc, 12, '['}, {0xffd, 12, ']'}, {0xffe, 12, '~'}, /* 13-bit codes */ {0x1ff8, 13, '+'}, {0x1ff9, 13, '<'}, {0x1ffa, 13, '\\'}, /* 14-bit codes */ {0x3ffc, 14, '\''}, {0x3ffd, 14, '|'}, /* 15-bit codes */ {0x7ffc, 15, '`'}, {0x7ffd, 15, '{'}, /* 19-bit codes */ {0x7fff0, 19, '}'}, /* 20-bit codes and above - less common characters */ {0xffff8, 20, 0x00}, {0xffff9, 20, 0x01}, {0xffffa, 20, 0x02}, {0xffffb, 20, 0x03}, {0xffffc, 20, 0x04}, {0xffffd, 20, 0x05}, {0xffffe, 20, 0x06}, {0xfffff, 20, 0x07}, {0x1ffff8, 21, 0x08}, {0x1ffff9, 21, 0x09}, {0x1ffffa, 21, 0x0a}, {0x1ffffb, 21, 0x0b}, {0x1ffffc, 21, 0x0c}, {0x1ffffd, 21, 0x0d}, {0x1ffffe, 21, 0x0e}, {0x1fffff, 21, 0x0f}, {0x3ffff8, 22, 0x10}, {0x3ffff9, 22, 0x11}, {0x3ffffa, 22, 0x12}, {0x3ffffb, 22, 0x13}, {0x3ffffc, 22, 0x14}, {0x3ffffd, 22, 0x15}, {0x3ffffe, 22, 0x16}, {0x3fffff, 22, 0x17}, {0x7ffff8, 23, 0x18}, {0x7ffff9, 23, 0x19}, {0x7ffffa, 23, 0x1a}, {0x7ffffb, 23, 0x1b}, {0x7ffffc, 23, 0x1c}, {0x7ffffd, 23, 0x1d}, {0x7ffffe, 23, 0x1e}, {0x7fffff, 23, 0x1f}, {0xfffff8, 24, 0x7f}, {0xfffff9, 24, 0x20}, {0xfffffa, 24, 0x21}, {0xfffffb, 24, 0x22}, {0xfffffc, 24, 0x23}, {0xfffffd, 24, 0x24}, {0xfffffe, 24, 0x25}, {0xffffff, 24, 0x26}, {0x1fffff8, 25, 0x27}, {0x1fffff9, 25, 0x28}, {0x1fffffa, 25, 0x29}, {0x1fffffb, 25, 0x2a}, {0x1fffffc, 25, 0x2b}, {0x1fffffd, 25, 0x2c}, {0x1fffffe, 25, 0x2d}, {0x1ffffff, 25, 0x2e}, {0x3fffff8, 26, 0x2f}, {0x3fffff9, 26, 0x30}, {0x3fffffa, 26, 0x31}, {0x3fffffb, 26, 0x32}, {0x3fffffc, 26, 0x33}, {0x3fffffd, 26, 0x34}, {0x3fffffe, 26, 0x35}, {0x3ffffff, 26, 0x36}, {0x7fffff8, 27, 0x37}, {0x7fffff9, 27, 0x38}, {0x7fffffa, 27, 0x39}, {0x7fffffb, 27, 0x3a}, {0x7fffffc, 27, 0x3b}, {0x7fffffd, 27, 0x3c}, {0x7fffffe, 27, 0x3d}, {0x7ffffff, 27, 0x3e}, {0xffffff8, 28, 0x3f}, {0xffffff9, 28, 0x40}, {0xffffffa, 28, 0x41}, {0xffffffb, 28, 0x42}, {0xffffffc, 28, 0x43}, {0xffffffd, 28, 0x44}, {0xffffffe, 28, 0x45}, {0xfffffff, 28, 0x46}, }; #define HUFFMAN_TABLE_SIZE (sizeof(huffman_table) / sizeof(huffman_table[0])) /* Huffman decoder using bit-by-bit decoding */ static int hpack_decode_huffman(const uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_len) { size_t dst_pos = 0; uint64_t bits = 0; int nbits = 0; size_t i; for (i = 0; i < src_len; i++) { if (nbits > 56) { /* Bit buffer would overflow on next byte */ return -1; } bits = (bits << 8) | src[i]; nbits += 8; /* Try to decode symbols */ while (nbits >= 5) { /* Minimum code length is 5 bits */ int found = 0; int len; /* Try different code lengths from longest to shortest for current bits */ for (len = (nbits > 30 ? 30 : nbits); len >= 5; len--) { uint32_t code = (uint32_t)((bits >> (nbits - len)) & (((uint64_t)1 << len) - 1)); size_t j; /* Search for matching code in table */ for (j = 0; j < HUFFMAN_TABLE_SIZE; j++) { if (huffman_table[j].nbits == (uint8_t)len && huffman_table[j].bits == code) { if (dst_pos >= dst_len) { return -1; } dst[dst_pos++] = huffman_table[j].symbol; nbits -= len; bits &= (((uint64_t)1 << nbits) - 1); /* Clear decoded bits */ found = 1; break; } } if (found) { break; } } if (!found) { /* No match found - might need more bits or it's padding */ if (i == src_len - 1) { /* Last byte - remaining bits should be padding (all 1s) */ uint32_t padding_mask = (1U << nbits) - 1; uint32_t remaining = (uint32_t)(bits & padding_mask); if (remaining == padding_mask) { /* Valid padding */ return dst_pos; } } break; /* Need more bits */ } } } return dst_pos; } static int hpack_decode_string(const uint8_t *data, int data_len, char **str) { uint64_t len; int huffman; int offset = 0; int ret; if (data_len < 1) { return -1; } huffman = (data[0] & 0x80) != 0; ret = hpack_decode_integer(data, data_len, 7, &len); if (ret < 0) { return -1; } offset += ret; if (offset + (int)len > data_len) { return -1; } if (huffman) { /* Huffman decoding */ /* Allocate buffer for decoded string (worst case: same size as encoded) */ uint8_t *decoded = malloc(len * 2 + 1); /* Extra space for safety */ if (!decoded) { return -1; } int decoded_len = hpack_decode_huffman(data + offset, len, decoded, len * 2); if (decoded_len < 0) { free(decoded); return -1; } *str = malloc(decoded_len + 1); if (!*str) { free(decoded); return -1; } memcpy(*str, decoded, decoded_len); (*str)[decoded_len] = '\0'; free(decoded); } else { /* Literal string */ *str = malloc(len + 1); if (*str == NULL) { return -1; } memcpy(*str, data + offset, len); (*str)[len] = '\0'; } offset += len; return offset; } /* HPACK dynamic table management */ void hpack_init_context(struct hpack_context *hpack) { hpack->dynamic_table = NULL; hpack->dynamic_table_size = 0; hpack->max_dynamic_table_size = 65536; /* Default size */ hpack->entry_count = 0; } void hpack_free_context(struct hpack_context *hpack) { struct hpack_dynamic_entry *entry = hpack->dynamic_table; while (entry) { struct hpack_dynamic_entry *next = entry->next; free(entry->name); free(entry->value); free(entry); entry = next; } hpack->dynamic_table = NULL; hpack->dynamic_table_size = 0; hpack->entry_count = 0; } static int hpack_add_dynamic_entry(struct hpack_context *hpack, const char *name, const char *value) { struct hpack_dynamic_entry *entry; size_t entry_size = strlen(name) + strlen(value) + 32; /* Evict entries if necessary */ while (hpack->dynamic_table_size + entry_size > hpack->max_dynamic_table_size && hpack->dynamic_table) { struct hpack_dynamic_entry *last = hpack->dynamic_table; struct hpack_dynamic_entry *prev = NULL; while (last->next) { prev = last; last = last->next; } if (prev) { prev->next = NULL; } else { hpack->dynamic_table = NULL; } hpack->dynamic_table_size -= last->size; hpack->entry_count--; free(last->name); free(last->value); free(last); } entry = malloc(sizeof(*entry)); if (!entry) { return -1; } entry->name = strdup(name); entry->value = strdup(value); if (!entry->name || !entry->value) { free(entry->name); free(entry->value); free(entry); return -1; } entry->size = entry_size; entry->next = hpack->dynamic_table; hpack->dynamic_table = entry; hpack->dynamic_table_size += entry_size; hpack->entry_count++; return 0; } static int hpack_get_entry(struct hpack_context *hpack, uint64_t index, const char **name, const char **value) { if (index == 0) { return -1; } if (index <= HPACK_STATIC_TABLE_SIZE) { *name = hpack_static_table[index - 1].name; *value = hpack_static_table[index - 1].value; return 0; } /* Dynamic table */ uint64_t dynamic_index = index - HPACK_STATIC_TABLE_SIZE - 1; struct hpack_dynamic_entry *entry = hpack->dynamic_table; uint64_t i = 0; while (entry && i < dynamic_index) { entry = entry->next; i++; } if (!entry) { return -1; } *name = entry->name; *value = entry->value; return 0; } static int hpack_find_index(struct hpack_context *hpack, const char *name, const char *value, int *index, int *name_only_index) { int i; *index = 0; *name_only_index = 0; /* Search static table */ for (i = 0; i < (int)HPACK_STATIC_TABLE_SIZE; i++) { if (strcmp(hpack_static_table[i].name, name) == 0) { if (*name_only_index == 0) { *name_only_index = i + 1; } if (strcmp(hpack_static_table[i].value, value) == 0) { *index = i + 1; return 0; } } } /* Search dynamic table */ struct hpack_dynamic_entry *entry = hpack->dynamic_table; i = 0; while (entry) { if (strcmp(entry->name, name) == 0) { if (*name_only_index == 0) { *name_only_index = HPACK_STATIC_TABLE_SIZE + 1 + i; } if (strcmp(entry->value, value) == 0) { *index = HPACK_STATIC_TABLE_SIZE + 1 + i; return 0; } } entry = entry->next; i++; } return 0; } /* HPACK encoding */ int hpack_encode_header(struct hpack_context *hpack, const char *name, const char *value, uint8_t *buf, int buf_size) { int index, name_only_index; int offset = 0; int ret; hpack_find_index(hpack, name, value, &index, &name_only_index); if (index > 0) { /* Indexed header field */ if (buf_size < 1) { return -1; } buf[offset] = 0x80; ret = hpack_encode_integer(index, 7, buf + offset, buf_size - offset); if (ret < 0) { return -1; } return ret; } if (name_only_index > 0) { /* Literal with incremental indexing - indexed name */ if (buf_size < 1) { return -1; } buf[offset] = 0x40; ret = hpack_encode_integer(name_only_index, 6, buf + offset, buf_size - offset); if (ret < 0) { return -1; } offset += ret; ret = hpack_encode_string(value, buf + offset, buf_size - offset); if (ret < 0) { return -1; } offset += ret; hpack_add_dynamic_entry(hpack, name, value); return offset; } /* Literal with incremental indexing - new name */ if (buf_size < 1) { return -1; } buf[offset++] = 0x40; ret = hpack_encode_string(name, buf + offset, buf_size - offset); if (ret < 0) { return -1; } offset += ret; ret = hpack_encode_string(value, buf + offset, buf_size - offset); if (ret < 0) { return -1; } offset += ret; hpack_add_dynamic_entry(hpack, name, value); return offset; } /* HPACK decoding */ void hpack_resize_dynamic_table(struct hpack_context *hpack, size_t new_size) { hpack->max_dynamic_table_size = new_size; /* Evict entries if necessary */ while (hpack->dynamic_table_size > hpack->max_dynamic_table_size && hpack->dynamic_table) { struct hpack_dynamic_entry *last = hpack->dynamic_table; struct hpack_dynamic_entry *prev = NULL; while (last->next) { prev = last; last = last->next; } if (prev) { prev->next = NULL; } else { hpack->dynamic_table = NULL; } hpack->dynamic_table_size -= last->size; hpack->entry_count--; free(last->name); free(last->value); free(last); } } int hpack_decode_headers(struct hpack_context *hpack, const uint8_t *data, int data_len, hpack_on_header_fn on_header, void *ctx) { int offset = 0; while (offset < data_len) { const char *name = NULL; const char *value = NULL; char *allocated_name = NULL; char *allocated_value = NULL; if ((data[offset] & 0x80) != 0) { /* Indexed header field */ uint64_t index; const char *static_name, *static_value; int ret = hpack_decode_integer(data + offset, data_len - offset, 7, &index); if (ret < 0) { return -1; } if (hpack_get_entry(hpack, index, &static_name, &static_value) < 0) { return -1; } offset += ret; name = static_name; value = static_value; } else if ((data[offset] & 0x40) != 0) { /* Literal with incremental indexing */ uint64_t index; int ret = hpack_decode_integer(data + offset, data_len - offset, 6, &index); if (ret < 0) { return -1; } offset += ret; if (index > 0) { const char *static_name, *static_value; if (hpack_get_entry(hpack, index, &static_name, &static_value) < 0) { return -1; } name = static_name; } else { ret = hpack_decode_string(data + offset, data_len - offset, &allocated_name); if (ret < 0) { return -1; } offset += ret; name = allocated_name; } ret = hpack_decode_string(data + offset, data_len - offset, &allocated_value); if (ret < 0) { free(allocated_name); return -1; } offset += ret; value = allocated_value; if (name && value) { hpack_add_dynamic_entry(hpack, name, value); } } else if ((data[offset] & 0x20) != 0) { /* Dynamic Table Size Update */ uint64_t new_size; int ret = hpack_decode_integer(data + offset, data_len - offset, 5, &new_size); if (ret < 0) { return -1; } offset += ret; hpack_resize_dynamic_table(hpack, new_size); continue; /* Continue to next field */ } else { /* Literal without indexing or never indexed */ uint64_t index; int prefix = 4; /* Both types use 4-bit prefix */ int ret = hpack_decode_integer(data + offset, data_len - offset, prefix, &index); if (ret < 0) { return -1; } offset += ret; if (index > 0) { const char *static_name, *static_value; if (hpack_get_entry(hpack, index, &static_name, &static_value) < 0) { return -1; } name = static_name; } else { ret = hpack_decode_string(data + offset, data_len - offset, &allocated_name); if (ret < 0) { return -1; } offset += ret; name = allocated_name; } ret = hpack_decode_string(data + offset, data_len - offset, &allocated_value); if (ret < 0) { free(allocated_name); return -1; } offset += ret; value = allocated_value; } /* Add header to stream */ if (on_header(ctx, name, value) < 0) { free(allocated_name); free(allocated_value); return -1; } /* Free allocated strings if they were copied */ if (allocated_name) { free(allocated_name); } if (allocated_value) { free(allocated_value); } } return 0; } ================================================ FILE: src/http_parse/hpack.h ================================================ #ifndef _HPACK_H_ #define _HPACK_H_ #include #include #ifdef __cplusplus extern "C" { #endif /* HPACK dynamic table entry */ struct hpack_dynamic_entry { char *name; char *value; size_t size; /* name_len + value_len + 32 */ struct hpack_dynamic_entry *next; }; /* HPACK context */ struct hpack_context { struct hpack_dynamic_entry *dynamic_table; size_t dynamic_table_size; size_t max_dynamic_table_size; int entry_count; }; /* Callback function for decoded headers */ typedef int (*hpack_on_header_fn)(void *ctx, const char *name, const char *value); /** * Initialize HPACK context * @param hpack HPACK context */ void hpack_init_context(struct hpack_context *hpack); /** * Free HPACK context * @param hpack HPACK context */ void hpack_free_context(struct hpack_context *hpack); /** * Resize dynamic table * @param hpack HPACK context * @param new_size New size */ void hpack_resize_dynamic_table(struct hpack_context *hpack, size_t new_size); /** * Encode a header * @param hpack HPACK context * @param name Header name * @param value Header value * @param buf Output buffer * @param buf_size Output buffer size * @return Number of bytes written, or -1 on error */ int hpack_encode_header(struct hpack_context *hpack, const char *name, const char *value, uint8_t *buf, int buf_size); /** * Decode headers * @param hpack HPACK context * @param data Input data * @param data_len Input data length * @param on_header Callback function for each decoded header * @param ctx User context passed to callback * @return 0 on success, -1 on error */ int hpack_decode_headers(struct hpack_context *hpack, const uint8_t *data, int data_len, hpack_on_header_fn on_header, void *ctx); #ifdef __cplusplus } #endif #endif /* _HPACK_H_ */ ================================================ FILE: src/http_parse/http1_parse.c ================================================ /************************************************************************* * * 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 . */ #include "http1_parse.h" #include "http_parse.h" #include "smartdns/util.h" #include #include static int _http_head_parse_response(struct http_head *http_head, char *key, char *value) { char *field_start = NULL; char *tmp_ptr = NULL; char *ret_msg = NULL; char *ret_code = NULL; if (strstr(key, "HTTP/") == NULL) { return -1; } for (tmp_ptr = value; *tmp_ptr != 0; tmp_ptr++) { if (field_start == NULL) { field_start = tmp_ptr; } if (*tmp_ptr != ' ') { continue; } *tmp_ptr = '\0'; ret_code = field_start; ret_msg = tmp_ptr + 1; field_start = NULL; break; } if (ret_code == NULL || ret_msg == NULL) { return -1; } if (is_numeric(ret_code) != 0) { return -1; } http_head->code = atol(ret_code); http_head->code_msg = ret_msg; http_head->version = key; http_head->head_type = HTTP_HEAD_RESPONSE; return 0; } static int _http_head_parse_request(struct http_head *http_head, char *key, char *value) { int method = HTTP_METHOD_INVALID; char *url = NULL; char *version = NULL; char *tmp_ptr = value; char *field_start = NULL; method = _http_method_parse(key); if (method == HTTP_METHOD_INVALID) { return _http_head_parse_response(http_head, key, value); } for (tmp_ptr = value; *tmp_ptr != 0; tmp_ptr++) { if (field_start == NULL) { field_start = tmp_ptr; } if (*tmp_ptr == ' ') { *tmp_ptr = '\0'; if (url == NULL) { url = field_start; } field_start = NULL; } } if (field_start && version == NULL) { version = field_start; tmp_ptr = field_start; } if (_http_head_parse_params(http_head, url, tmp_ptr - url) != 0) { return -2; } http_head->method = method; http_head->url = url; http_head->version = version; http_head->head_type = HTTP_HEAD_REQUEST; return 0; } static int _http_head_parse(struct http_head *http_head) { int i = 0; char *key = NULL; char *value = NULL; char *data; int has_first_line = 0; int inkey = 1; int invalue = 0; data = (char *)http_head->buff; for (i = 0; i < http_head->head_len; i++, data++) { if (inkey) { if (key == NULL && *data != ' ' && *data != '\r' && *data != '\n') { key = data; continue; } if (*data == ':' || *data == ' ') { *data = '\0'; inkey = 0; invalue = 1; continue; } } if (invalue) { if (value == NULL && *data != ' ') { value = data; continue; } if (*data == '\r' || *data == '\n') { *data = '\0'; inkey = 1; invalue = 0; } } if (key && value && invalue == 0) { if (has_first_line == 0) { if (_http_head_parse_request(http_head, key, value) != 0) { return -2; } has_first_line = 1; } else { if (http_head_add_fields(http_head, key, value) != 0) { return -2; } } key = NULL; value = NULL; inkey = 1; invalue = 0; } } return 0; } static int _http1_get_chunk_len(const uint8_t *data, int data_len, int32_t *chunk_len) { int offset = 0; int32_t chunk_value = 0; int is_num_start = 0; for (offset = 0; offset < data_len; offset++) { if (data[offset] == ' ') { continue; } if (data[offset] == '\r') { if (offset + 1 < data_len && data[offset + 1] == '\n') { offset += 2; break; } if (is_num_start == 0) { return -2; } return -2; } int value = decode_hex(data[offset]); if (value < 0) { return -2; } if (is_num_start == 0) { is_num_start = 1; } chunk_value = (chunk_value << 4) + value; } if (offset >= data_len) { return -1; } *chunk_len = chunk_value; return offset; } int http_head_parse_http1_1(struct http_head *http_head, const uint8_t *data, int in_data_len) { int i = 0; uint8_t *buff_end = NULL; int left_size = 0; int process_data_len = 0; int data_len = in_data_len; int is_chunked = 0; left_size = http_head->buff_size - http_head->buff_len; if (left_size < data_len) { return -3; } buff_end = http_head->buff + http_head->buff_len; if (http_head->head_ok == 0) { for (i = 0; i < in_data_len; i++, data++) { *(buff_end + i) = *data; if (isprint(*data) == 0 && isspace(*data) == 0) { return -2; } if (*data == '\n') { if (http_head->buff_len + i < 2) { continue; } if (*(buff_end + i - 2) == '\n') { http_head->head_ok = 1; http_head->head_len = http_head->buff_len + i - 2; i++; buff_end += i; data_len -= i; data++; if (_http_head_parse(http_head) != 0) { return -2; } const char *content_len = NULL; content_len = http_head_get_fields_value(http_head, "Content-Length"); if (content_len) { http_head->expect_data_len = atol(content_len); } else { http_head->expect_data_len = 0; } if (http_head->expect_data_len < 0) { return -2; } break; } } } process_data_len += i; if (process_data_len >= http_head->buff_size) { return -3; } if (http_head->head_ok == 0) { // Read data again */ http_head->buff_len += process_data_len; return -1; } } const char *transfer_encoding = http_head_get_fields_value(http_head, "Transfer-Encoding"); if (transfer_encoding != NULL && strncasecmp(transfer_encoding, "chunked", sizeof("chunked")) == 0) { is_chunked = 1; } if (http_head->head_ok == 1) { if (is_chunked == 0) { int get_data_len = (http_head->expect_data_len > data_len) ? data_len : http_head->expect_data_len; if (get_data_len == 0 && data_len > 0) { get_data_len = data_len; } if (http_head->data == NULL) { http_head->data = buff_end; } memcpy(buff_end, data, get_data_len); process_data_len += get_data_len; http_head->data_len += get_data_len; buff_end += get_data_len; } else { const uint8_t *body_data = buff_end; uint32_t body_data_len = 0; while (true) { int32_t chunk_len = 0; int offset = 0; offset = _http1_get_chunk_len(data, data_len, &chunk_len); if (offset < 0) { return offset; } data += offset; data_len -= offset; process_data_len += offset; if (chunk_len == 0) { http_head->data = body_data; http_head->data_len = body_data_len; break; } if (data_len < chunk_len) { return -1; } if (data_len < chunk_len + 2) { return -1; } if (data[chunk_len] != '\r' || data[chunk_len + 1] != '\n') { return -2; } /* Check buffer space */ if (http_head->buff_len + process_data_len + chunk_len >= http_head->buff_size) { return -3; } memcpy(buff_end, data, chunk_len); body_data_len += chunk_len; buff_end += chunk_len; data_len -= chunk_len; data += chunk_len + 2; data_len -= 2; process_data_len += chunk_len + 2; } } /* try append null byte */ if (process_data_len < http_head->buff_size - 1) { buff_end[0] = '\0'; } } if (process_data_len >= http_head->buff_size) { return -3; } http_head->buff_len += process_data_len; if (http_head->data_len < http_head->expect_data_len) { return -1; } return process_data_len; } int http_head_serialize_http1_1(struct http_head *http_head, char *buffer, int buffer_len) { int len = 0; char *buff_start = buffer; struct http_head_fields *fields = NULL; struct http_params *params = NULL; if (http_head->head_type == HTTP_HEAD_INVALID) { return -1; } if (http_head->head_type == HTTP_HEAD_REQUEST) { if (http_head->method == HTTP_METHOD_INVALID || http_head->url == NULL || http_head->version == NULL) { return -1; } len = snprintf(buffer, buffer_len, "%s %s", http_method_str(http_head->method), http_head->url); if (len < 0) { return -2; } buffer += len; buffer_len -= len; if (buffer_len < 2) { return -3; } int count = 0; list_for_each_entry(params, &http_head->params.list, list) { if (count == 0) { len = snprintf(buffer, buffer_len, "?%s=%s", params->name, params->value); } else { len = snprintf(buffer, buffer_len, "&%s=%s", params->name, params->value); } count++; buffer += len; buffer_len -= len; if (buffer_len < 2) { return -3; } } if (buffer_len < 2) { return -3; } len = snprintf(buffer, buffer_len, " %s\r\n", http_head->version); if (len < 0) { return -2; } buffer += len; buffer_len -= len; } if (http_head->head_type == HTTP_HEAD_RESPONSE) { if (http_head->code < 0 || http_head->code_msg == NULL || http_head->version == NULL) { return -1; } len = snprintf(buffer, buffer_len, "%s %d %s\r\n", http_head->version, http_head->code, http_head->code_msg); if (len < 0) { return -2; } buffer += len; buffer_len -= len; if (buffer_len < 2) { return -3; } } list_for_each_entry(fields, &http_head->field_head.list, list) { len = snprintf(buffer, buffer_len, "%s: %s\r\n", fields->name, fields->value); if (len < 0) { return -2; } buffer += len; buffer_len -= len; if (buffer_len < 2) { return -3; } } if (buffer_len < 2) { return -3; } *(buffer) = '\r'; *(buffer + 1) = '\n'; buffer += 2; buffer_len -= 2; if (http_head->data_len > buffer_len) { return -3; } if (http_head->data && http_head->data_len > 0) { memcpy(buffer, http_head->data, http_head->data_len); buffer += http_head->data_len; buffer_len -= http_head->data_len; } return buffer - buff_start; } ================================================ FILE: src/http_parse/http1_parse.h ================================================ /************************************************************************* * * 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 . */ #ifndef _HTTP_PARSE_HTTP1_H_ #define _HTTP_PARSE_HTTP1_H_ #include "http_parse.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int http_head_serialize_http1_1(struct http_head *http_head, char *buffer, int buffer_len); int http_head_parse_http1_1(struct http_head *http_head, const uint8_t *data, int data_len); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/http_parse/http2.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/http2.h" #include "smartdns/util.h" #include "smartdns/tlog.h" #include "hpack.h" #include "http_parse.h" #include #include #include #include #include #include #ifdef WITH_ZLIB #include #endif const char *http2_error_to_string(int ret) { switch (ret) { case HTTP2_ERR_NONE: return "no error"; case HTTP2_ERR_EAGAIN: return "operation would block"; case HTTP2_ERR_EOF: return "connection closed by client"; case HTTP2_ERR_IO: return "I/O error"; case HTTP2_ERR_PROTOCOL: return "protocol error"; case HTTP2_ERR_HTTP1: return "client sent HTTP/1.1 after ALPN h2"; default: return "unknown error"; } } /* HTTP/2 Frame Types */ #define HTTP2_FRAME_DATA 0x00 #define HTTP2_FRAME_HEADERS 0x01 #define HTTP2_FRAME_PRIORITY 0x02 #define HTTP2_FRAME_RST_STREAM 0x03 #define HTTP2_FRAME_SETTINGS 0x04 #define HTTP2_FRAME_PUSH_PROMISE 0x05 #define HTTP2_FRAME_PING 0x06 #define HTTP2_FRAME_GOAWAY 0x07 #define HTTP2_FRAME_WINDOW_UPDATE 0x08 #define HTTP2_FRAME_CONTINUATION 0x09 /* HTTP/2 Error Codes for RST_STREAM and GOAWAY */ #define HTTP2_RST_NO_ERROR 0 #define HTTP2_RST_PROTOCOL_ERROR 1 #define HTTP2_RST_INTERNAL_ERROR 2 #define HTTP2_RST_FLOW_CONTROL_ERROR 3 #define HTTP2_RST_COMPRESSION_ERROR 9 /* HTTP/2 Frame Flags */ #define HTTP2_FLAG_END_STREAM 0x01 #define HTTP2_FLAG_END_HEADERS 0x04 #define HTTP2_FLAG_PADDED 0x08 #define HTTP2_FLAG_PRIORITY 0x20 #define HTTP2_FLAG_ACK 0x01 /* HTTP/2 Settings */ #define HTTP2_SETTINGS_HEADER_TABLE_SIZE 0x01 #define HTTP2_SETTINGS_ENABLE_PUSH 0x02 #define HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS 0x03 #define HTTP2_SETTINGS_INITIAL_WINDOW_SIZE 0x04 #define HTTP2_SETTINGS_MAX_FRAME_SIZE 0x05 #define HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE 0x06 /* Default values */ #define HTTP2_DEFAULT_WINDOW_SIZE 65535 #define HTTP2_DEFAULT_MAX_FRAME_SIZE 16384 #define HTTP2_FRAME_HEADER_SIZE 9 #define HTTP2_MAX_HEADER_TABLE_SIZE 65536 #define HTTP2_CONNECTION_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" #define HTTP2_CONNECTION_PREFACE_LEN 24 /* Stream states */ typedef enum { HTTP2_STREAM_IDLE, HTTP2_STREAM_OPEN, HTTP2_STREAM_HALF_CLOSED_LOCAL, HTTP2_STREAM_HALF_CLOSED_REMOTE, HTTP2_STREAM_CLOSED } http2_stream_state_t; /* Stream structure */ struct http2_stream { struct http2_ctx *ctx; int refcount; /* Atomic reference count */ uint32_t stream_id; http2_stream_state_t state; /* Headers using hashmap like http_head */ struct http_head_fields header_list; DECLARE_HASHTABLE(header_map, 4); uint8_t *body_buffer; int body_buffer_size; int body_buffer_len; int body_read_offset; int end_stream_received; int end_stream_sent; int end_stream_read_handled; /* Flag to track if EOF has been reported to app */ int accepted; /* Flag to track if stream has been accepted by app */ int window_size; int body_decompressed; /* Flag to track if body has been decompressed */ void *ex_data; struct hlist_node hash_node; struct list_head node; }; /* HTTP/2 context */ struct http2_ctx { pthread_mutex_t mutex; int refcount; /* Atomic reference count */ int is_client; char *server; http2_bio_read_fn bio_read; http2_bio_write_fn bio_write; void *private_data; /* Connection state */ int status; /* 0: connected, <0: error code */ int handshake_complete; int settings_received; int preface_received; /* Server: has received client preface */ uint32_t next_stream_id; int connection_window_size; uint32_t peer_max_frame_size; int peer_initial_window_size; int active_streams; struct http2_settings settings; /* HTTP/2 settings */ /* I/O state */ int want_read; int want_write; uint8_t *pending_write_buffer; int pending_write_len; int pending_write_capacity; /* HPACK */ struct hpack_context encoder; struct hpack_context decoder; /* Streams */ DECLARE_HASHTABLE(stream_map, 8); struct list_head streams; /* Frame buffers */ uint8_t read_buffer[HTTP2_DEFAULT_MAX_FRAME_SIZE + HTTP2_FRAME_HEADER_SIZE]; int read_buffer_len; uint8_t write_buffer[HTTP2_DEFAULT_MAX_FRAME_SIZE + HTTP2_FRAME_HEADER_SIZE]; int write_buffer_len; }; /* Public API implementation */ struct http2_ctx_init_params { const char *server; http2_bio_read_fn bio_read; http2_bio_write_fn bio_write; void *private_data; const struct http2_settings *settings; int is_client; uint32_t next_stream_id; }; /* Forward declarations */ static int _http2_send_settings(struct http2_ctx *ctx, int ack); static int _http2_send_window_update(struct http2_ctx *ctx, uint32_t stream_id, int increment); static int _http2_process_frames(struct http2_ctx *ctx); static int http2_send_rst_stream(struct http2_ctx *ctx, uint32_t stream_id, uint32_t error_code); static void _http2_free_headers(struct http2_stream *stream); static struct http2_stream *_http2_find_stream(struct http2_ctx *ctx, uint32_t stream_id); static int _http2_stream_add_header(struct http2_stream *stream, const char *name, const char *value); /* Utility functions */ static uint32_t read_uint32(const uint8_t *data) { return ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | ((uint32_t)data[2] << 8) | (uint32_t)data[3]; } static void write_uint32(uint8_t *data, uint32_t value) { data[0] = (value >> 24) & 0xFF; data[1] = (value >> 16) & 0xFF; data[2] = (value >> 8) & 0xFF; data[3] = value & 0xFF; } static uint32_t read_uint24(const uint8_t *data) { return ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | (uint32_t)data[2]; } static void write_uint24(uint8_t *data, uint32_t value) { data[0] = (value >> 16) & 0xFF; data[1] = (value >> 8) & 0xFF; data[2] = value & 0xFF; } /* Safe buffer size calculation to prevent integer overflow */ static int safe_buffer_size(int current, int factor) { if (current < 0 || factor <= 0) { return -1; /* Invalid parameters */ } if ((size_t)current > (size_t)INT_MAX / (size_t)factor) { return -1; /* Overflow */ } return current * factor; } /* HPACK callback */ static int _http2_on_header(void *ctx, const char *name, const char *value) { struct http2_stream *stream = (struct http2_stream *)ctx; if (!stream) { return -1; } return _http2_stream_add_header(stream, name, value); } static void _http2_free_headers(struct http2_stream *stream) { struct http_head_fields *fields = NULL, *tmp; list_for_each_entry_safe(fields, tmp, &stream->header_list.list, list) { list_del(&fields->list); free((void *)fields->name); free((void *)fields->value); free(fields); } hash_init(stream->header_map); } static int _http2_stream_add_header(struct http2_stream *stream, const char *name, const char *value) { uint32_t key = 0; struct http_head_fields *fields = NULL; if (name == NULL || value == NULL) { return -1; } fields = zalloc(1, sizeof(*fields)); if (fields == NULL) { return -1; } fields->name = strdup(name); fields->value = strdup(value); if (!fields->name || !fields->value) { free((void *)fields->name); free((void *)fields->value); free(fields); return -1; } list_add_tail(&fields->list, &stream->header_list.list); key = hash_string_case(name); hash_add(stream->header_map, &fields->node, key); return 0; } static const char *_http2_stream_get_header_value(struct http2_stream *stream, const char *name) { uint32_t key; struct http_head_fields *field = NULL; key = hash_string_case(name); hash_for_each_possible(stream->header_map, field, node, key) { if (strncasecmp(field->name, name, 128) == 0) { return field->value; } } return NULL; } void http2_stream_headers_walk(struct http2_stream *stream, header_walk_fn fn, void *arg) { struct list_head *pos; if (!stream || !fn) { return; } list_for_each(pos, &stream->header_list.list) { struct http_head_fields *pair = list_entry(pos, struct http_head_fields, list); fn(arg, pair->name, pair->value); } } /* Frame handling */ static int _http2_write_frame_header(uint8_t *buf, int length, uint8_t type, uint8_t flags, uint32_t stream_id) { write_uint24(buf, length); buf[3] = type; buf[4] = flags; write_uint32(buf + 5, stream_id & 0x7FFFFFFF); return HTTP2_FRAME_HEADER_SIZE; } static int _http2_send_frame(struct http2_ctx *ctx, const uint8_t *data, int len) { /* Check if connection is already closed */ if (!ctx) { return -1; } pthread_mutex_lock(&ctx->mutex); if (ctx->status < 0) { pthread_mutex_unlock(&ctx->mutex); return -1; } int total_sent = 0; int unsent = 0; /* First, try to flush any pending writes */ if (ctx->pending_write_len > 0) { int ret = ctx->bio_write(ctx->private_data, ctx->pending_write_buffer, ctx->pending_write_len); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { ctx->want_write = 1; /* Still have pending data, buffer new data too */ goto buffer_new_data; } /* Real error */ pthread_mutex_unlock(&ctx->mutex); return -1; } if (ret > 0) { /* Partial or complete write */ if (ret < ctx->pending_write_len) { /* Partial write, move remaining data */ memmove(ctx->pending_write_buffer, ctx->pending_write_buffer + ret, ctx->pending_write_len - ret); ctx->pending_write_len -= ret; ctx->want_write = 1; goto buffer_new_data; } else { /* Complete write of pending data */ ctx->pending_write_len = 0; ctx->want_write = 0; } } else if (ret == 0) { /* Connection closed */ ctx->status = HTTP2_ERR_EOF; pthread_mutex_unlock(&ctx->mutex); return -1; } } /* Now try to send the new data */ while (total_sent < len) { int ret = ctx->bio_write(ctx->private_data, data + total_sent, len - total_sent); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { ctx->want_write = 1; /* Buffer remaining data */ goto buffer_new_data; } /* Real error */ pthread_mutex_unlock(&ctx->mutex); return -1; } if (ret == 0) { /* Connection closed */ ctx->status = HTTP2_ERR_EOF; pthread_mutex_unlock(&ctx->mutex); return -1; } total_sent += ret; } ctx->want_write = 0; pthread_mutex_unlock(&ctx->mutex); return len; buffer_new_data: /* Buffer the unsent data */ unsent = len - total_sent; if (unsent > 0) { /* Ensure buffer capacity */ int needed = ctx->pending_write_len + unsent; if (needed > ctx->pending_write_capacity) { int new_capacity = ctx->pending_write_capacity ? safe_buffer_size(ctx->pending_write_capacity, 2) : 8192; if (new_capacity < 0) { pthread_mutex_unlock(&ctx->mutex); return -1; } while (new_capacity < needed) { int temp = safe_buffer_size(new_capacity, 2); if (temp < 0) { pthread_mutex_unlock(&ctx->mutex); return -1; } new_capacity = temp; } uint8_t *new_buffer = realloc(ctx->pending_write_buffer, new_capacity); if (!new_buffer) { pthread_mutex_unlock(&ctx->mutex); return -1; } ctx->pending_write_buffer = new_buffer; ctx->pending_write_capacity = new_capacity; } /* Append unsent data to buffer */ memcpy(ctx->pending_write_buffer + ctx->pending_write_len, data + total_sent, unsent); ctx->pending_write_len += unsent; } ctx->want_write = 1; pthread_mutex_unlock(&ctx->mutex); return len; /* Return success - data is buffered */ } static int _http2_send_settings(struct http2_ctx *ctx, int ack) { uint8_t frame[HTTP2_FRAME_HEADER_SIZE + 256]; /* Increased size for ENABLE_PUSH */ int offset = HTTP2_FRAME_HEADER_SIZE; uint8_t flags = ack ? HTTP2_FLAG_ACK : 0; if (!ack) { /* Client: Disable Server Push */ if (ctx->is_client) { write_uint32(frame + offset, (HTTP2_SETTINGS_ENABLE_PUSH << 16) | 0); write_uint32(frame + offset + 2, 0); /* 0 = disabled */ offset += 6; } /* SETTINGS_HEADER_TABLE_SIZE */ write_uint32(frame + offset, (HTTP2_SETTINGS_HEADER_TABLE_SIZE << 16) | 0); write_uint32(frame + offset + 2, HTTP2_MAX_HEADER_TABLE_SIZE); offset += 6; /* SETTINGS_INITIAL_WINDOW_SIZE */ write_uint32(frame + offset, (HTTP2_SETTINGS_INITIAL_WINDOW_SIZE << 16) | 0); write_uint32(frame + offset + 2, HTTP2_DEFAULT_WINDOW_SIZE); offset += 6; /* SETTINGS_MAX_FRAME_SIZE */ write_uint32(frame + offset, (HTTP2_SETTINGS_MAX_FRAME_SIZE << 16) | 0); write_uint32(frame + offset + 2, HTTP2_DEFAULT_MAX_FRAME_SIZE); offset += 6; if (ctx->settings.max_concurrent_streams > 0) { /* SETTINGS_MAX_CONCURRENT_STREAMS */ write_uint32(frame + offset, (HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS << 16) | 0); write_uint32(frame + offset + 2, ctx->settings.max_concurrent_streams); offset += 6; } } _http2_write_frame_header(frame, offset - HTTP2_FRAME_HEADER_SIZE, HTTP2_FRAME_SETTINGS, flags, 0); return _http2_send_frame(ctx, frame, offset); } static int _http2_send_window_update(struct http2_ctx *ctx, uint32_t stream_id, int increment) { uint8_t frame[HTTP2_FRAME_HEADER_SIZE + 4]; _http2_write_frame_header(frame, 4, HTTP2_FRAME_WINDOW_UPDATE, 0, stream_id); write_uint32(frame + HTTP2_FRAME_HEADER_SIZE, increment & 0x7FFFFFFF); return _http2_send_frame(ctx, frame, sizeof(frame)); } static int _http2_send_goaway(struct http2_ctx *ctx, uint32_t last_stream_id, uint32_t error_code, const uint8_t *debug_data, int debug_len) { uint8_t frame[HTTP2_FRAME_HEADER_SIZE + 8 + 256]; int offset = HTTP2_FRAME_HEADER_SIZE; write_uint32(frame + offset, last_stream_id & 0x7FFFFFFF); write_uint32(frame + offset + 4, error_code); if (debug_len > 0 && debug_len <= 256) { memcpy(frame + offset + 8, debug_data, debug_len); } else { debug_len = 0; } _http2_write_frame_header(frame, 8 + debug_len, HTTP2_FRAME_GOAWAY, 0, 0); return _http2_send_frame(ctx, frame, HTTP2_FRAME_HEADER_SIZE + 8 + debug_len); } static int _http2_send_data_frame(struct http2_ctx *ctx, uint32_t stream_id, const uint8_t *data, int len, int end_stream) { uint8_t *frame = zalloc(1, HTTP2_FRAME_HEADER_SIZE + len); if (!frame) { return -1; } uint8_t flags = end_stream ? HTTP2_FLAG_END_STREAM : 0; _http2_write_frame_header(frame, len, HTTP2_FRAME_DATA, flags, stream_id); if (len > 0) { memcpy(frame + HTTP2_FRAME_HEADER_SIZE, data, len); } int ret = _http2_send_frame(ctx, frame, HTTP2_FRAME_HEADER_SIZE + len); free(frame); return ret; } static int _http2_send_headers_frame(struct http2_ctx *ctx, uint32_t stream_id, struct http2_stream *stream, int end_stream, int end_headers) { uint8_t *header_block = zalloc(1, 4096); int header_block_size = 4096; int header_block_len = 0; struct http_head_fields *field; uint8_t flags = 0; int ret = -1; if (!header_block) { return -1; } if (end_stream) { flags |= HTTP2_FLAG_END_STREAM; } if (end_headers) { flags |= HTTP2_FLAG_END_HEADERS; } /* Encode headers */ list_for_each_entry(field, &stream->header_list.list, list) { if (header_block_len + 4096 > header_block_size) { int new_size = safe_buffer_size(header_block_size, 2); if (new_size < 0) { goto cleanup; } uint8_t *new_block = realloc(header_block, new_size); if (!new_block) { goto cleanup; } header_block = new_block; header_block_size = new_size; } int encode_ret = hpack_encode_header(&ctx->encoder, field->name, field->value, header_block + header_block_len, header_block_size - header_block_len); if (encode_ret < 0) { goto cleanup; } header_block_len += encode_ret; } /* Send HEADERS frame */ uint8_t frame[HTTP2_FRAME_HEADER_SIZE]; _http2_write_frame_header(frame, header_block_len, HTTP2_FRAME_HEADERS, flags, stream_id); if (_http2_send_frame(ctx, frame, HTTP2_FRAME_HEADER_SIZE) < 0) { goto cleanup; } if (header_block_len > 0 && _http2_send_frame(ctx, header_block, header_block_len) < 0) { goto cleanup; } ret = 0; cleanup: free(header_block); return ret; } static struct http2_stream *_http2_find_stream(struct http2_ctx *ctx, uint32_t stream_id) { struct http2_stream *stream; if (!ctx) { return NULL; } pthread_mutex_lock(&ctx->mutex); hash_for_each_possible(ctx->stream_map, stream, hash_node, stream_id) { if (stream->stream_id == stream_id) { pthread_mutex_unlock(&ctx->mutex); return stream; } } pthread_mutex_unlock(&ctx->mutex); return NULL; } static struct http2_stream *_http2_create_stream(struct http2_ctx *ctx, uint32_t stream_id) { /* Check concurrent streams limit */ if (ctx->active_streams >= ctx->settings.max_concurrent_streams && ctx->settings.max_concurrent_streams > 0) { tlog(TLOG_DEBUG, "HTTP/2: Max concurrent streams limit reached (%d)", ctx->settings.max_concurrent_streams); errno = ENOSPC; return NULL; } struct http2_stream *stream = zalloc(1, sizeof(*stream)); if (!stream) { return NULL; } stream->ctx = ctx; stream->refcount = 0; /* Initial reference count */ stream->stream_id = stream_id; stream->state = HTTP2_STREAM_IDLE; /* Determine if stream is accepted (locally initiated) or needs accept (peer initiated) */ if (ctx->is_client) { /* Client: Odd IDs are local (accepted), Even IDs are remote (need accept) */ stream->accepted = (stream_id % 2) != 0; } else { /* Server: Even IDs are local (accepted), Odd IDs are remote (need accept) */ stream->accepted = (stream_id % 2) == 0; } stream->window_size = ctx->peer_initial_window_size; stream->body_buffer_size = 8192; stream->body_buffer = zalloc(1, stream->body_buffer_size); if (!stream->body_buffer) { free(stream); return NULL; } /* Initialize header structures */ INIT_LIST_HEAD(&stream->header_list.list); hash_init(stream->header_map); http2_stream_get(stream); /* Hold ownership for ctx */ pthread_mutex_lock(&ctx->mutex); hash_add(ctx->stream_map, &stream->hash_node, stream->stream_id); list_add(&stream->node, &ctx->streams); ctx->active_streams++; pthread_mutex_unlock(&ctx->mutex); return stream; } static int _http2_remove_stream(struct http2_stream *stream, int do_put) { struct http2_ctx *ctx = NULL; if (!stream) { return -1; } ctx = stream->ctx; if (ctx) { pthread_mutex_lock(&ctx->mutex); } /* Try to remove from hash map */ if (!hlist_unhashed(&stream->hash_node)) { hash_del(&stream->hash_node); } /* Try to remove from list */ if (!list_empty(&stream->node)) { list_del_init(&stream->node); stream->ctx = NULL; /* Break link to ctx to prevent UAF if ctx dies first */ if (ctx) { ctx->active_streams--; } /* Only release ownership if we successfully removed it from the list. This prevents double-free if called concurrently or recursively. */ if (do_put) { http2_stream_put(stream); } } else { /* If already removed from list, we assume we don't own the list reference anymore */ } if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return 0; } static int _http2_process_data_frame(struct http2_ctx *ctx, int stream_id, const uint8_t *data, int len, uint8_t flags) { struct http2_stream *stream = _http2_find_stream(ctx, stream_id); if (!stream) { return -1; } /* Stream ID 0 is invalid for DATA frames */ if (stream_id == 0) { _http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } /* Check for invalid length */ if (len < 0) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_PROTOCOL_ERROR); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } /* Check for integer overflow in buffer size */ if (stream->body_buffer_len > INT_MAX - len) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR); return -1; } /* Limit body buffer to 1MB to prevent OOM DOS */ if (stream->body_buffer_len + len > 1024 * 1024) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } /* Check flow control */ if (len < 0 || stream->window_size <= 0 || ctx->connection_window_size <= 0) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_FLOW_CONTROL_ERROR); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } if (len > stream->window_size || len > ctx->connection_window_size) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_FLOW_CONTROL_ERROR); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } /* Append to body buffer */ if (stream->body_buffer_len + len > stream->body_buffer_size) { int new_size = safe_buffer_size(stream->body_buffer_size, 2); if (new_size < 0) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR); return -1; } while (new_size < stream->body_buffer_len + len) { int temp = safe_buffer_size(new_size, 2); if (temp < 0) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR); return -1; } new_size = temp; } uint8_t *new_buffer = realloc(stream->body_buffer, new_size); if (!new_buffer) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR); return -1; } stream->body_buffer = new_buffer; stream->body_buffer_size = new_size; } memcpy(stream->body_buffer + stream->body_buffer_len, data, len); stream->body_buffer_len += len; if (flags & HTTP2_FLAG_END_STREAM) { stream->end_stream_received = 1; if (stream->state == HTTP2_STREAM_OPEN) { stream->state = HTTP2_STREAM_HALF_CLOSED_REMOTE; } else if (stream->state == HTTP2_STREAM_HALF_CLOSED_LOCAL) { stream->state = HTTP2_STREAM_CLOSED; } } /* Update flow control */ /* Send WINDOW_UPDATE immediately to prevent flow control deadlock */ /* Connection-level WINDOW_UPDATE */ if (_http2_send_window_update(ctx, 0, len) < 0) { return -1; } ctx->connection_window_size += len; /* Stream-level WINDOW_UPDATE */ if (_http2_send_window_update(ctx, stream_id, len) < 0) { ctx->connection_window_size -= len; return -1; } stream->window_size += len; return 0; } static int _http2_process_headers_frame(struct http2_ctx *ctx, int stream_id, const uint8_t *data, int len, uint8_t flags, uint8_t frame_type) { /* Handle Padding and Priority fields (only for HEADERS frame) */ int pad_len = 0; if (frame_type == HTTP2_FRAME_HEADERS) { if (flags & HTTP2_FLAG_PADDED) { if (len < 1) { return -1; } pad_len = data[0]; data++; len--; } if (flags & HTTP2_FLAG_PRIORITY) { if (len < 5) { return -1; } data += 5; len -= 5; } } else if (frame_type == HTTP2_FRAME_CONTINUATION) { /* CONTINUATION frames must not have PADDED or PRIORITY flags */ if (flags & (HTTP2_FLAG_PADDED | HTTP2_FLAG_PRIORITY)) { _http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } } if (len < pad_len) { return -1; } len -= pad_len; struct http2_stream *stream = _http2_find_stream(ctx, stream_id); if (!stream) { stream = _http2_create_stream(ctx, stream_id); if (!stream) { return -1; } } /* Clear old headers only if this is a new HEADERS frame */ if (frame_type == HTTP2_FRAME_HEADERS) { _http2_free_headers(stream); } if (len > 0) { /* Only decode if there's data */ if (hpack_decode_headers(&ctx->decoder, data, len, _http2_on_header, stream) < 0) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_COMPRESSION_ERROR); return -1; } } if (stream->state == HTTP2_STREAM_IDLE) { stream->state = HTTP2_STREAM_OPEN; } if (flags & HTTP2_FLAG_END_STREAM) { stream->end_stream_received = 1; if (stream->state == HTTP2_STREAM_OPEN) { stream->state = HTTP2_STREAM_HALF_CLOSED_REMOTE; } } return 0; } static int _http2_process_settings_frame(struct http2_ctx *ctx, const uint8_t *data, int len, uint8_t flags) { if (!ctx || !data) { return -1; } if (flags & HTTP2_FLAG_ACK) { return 0; } if (len < 0 || len % 6 != 0) { _http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } /* Process settings */ int offset = 0; while (offset + 6 <= len) { uint16_t id = (data[offset] << 8) | data[offset + 1]; uint32_t value = read_uint32(data + offset + 2); offset += 6; switch (id) { case HTTP2_SETTINGS_HEADER_TABLE_SIZE: ctx->encoder.max_dynamic_table_size = value; break; case HTTP2_SETTINGS_ENABLE_PUSH: /* Server: must ignore this setting */ break; case HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: ctx->settings.max_concurrent_streams = value; break; case HTTP2_SETTINGS_INITIAL_WINDOW_SIZE: ctx->peer_initial_window_size = value > INT_MAX ? INT_MAX : value; break; case HTTP2_SETTINGS_MAX_FRAME_SIZE: ctx->peer_max_frame_size = value > HTTP2_DEFAULT_MAX_FRAME_SIZE ? HTTP2_DEFAULT_MAX_FRAME_SIZE : value; break; default: break; } } /* Send SETTINGS ACK */ ctx->settings_received = 1; return _http2_send_settings(ctx, 1); } static int _http2_process_window_update_frame(struct http2_ctx *ctx, int stream_id, const uint8_t *data, int len) { if (!ctx || !data) { return -1; } if (len != 4) { _http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } uint32_t increment = read_uint32(data) & 0x7FFFFFFF; if (increment == 0) { /* RFC 9113: A receiver MUST treat the receipt of a WINDOW_UPDATE frame with an increment of 0 as a stream error of type PROTOCOL_ERROR */ if (stream_id == 0) { _http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0); } else { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_PROTOCOL_ERROR); } ctx->status = HTTP2_ERR_PROTOCOL; return -1; } if (stream_id == 0) { if (ctx->connection_window_size > INT_MAX - (int)increment) { _http2_send_goaway(ctx, 0, HTTP2_RST_FLOW_CONTROL_ERROR, NULL, 0); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } ctx->connection_window_size += (int)increment; } else { struct http2_stream *stream = _http2_find_stream(ctx, stream_id); if (stream) { if (stream->window_size > INT_MAX - (int)increment) { http2_send_rst_stream(ctx, stream_id, HTTP2_RST_FLOW_CONTROL_ERROR); return -1; } stream->window_size += (int)increment; } } return 0; } /* Verify HTTP/2 connection preface (server side) */ static int http2_verify_connection_preface(struct http2_ctx *ctx) { if (!ctx) { return -1; } /* Server: first read and verify connection preface */ if (ctx->is_client || ctx->preface_received) { return 0; /* Not applicable or already verified */ } /* Need to read 24-byte connection preface */ if (ctx->read_buffer_len < HTTP2_CONNECTION_PREFACE_LEN) { int to_read = HTTP2_CONNECTION_PREFACE_LEN - ctx->read_buffer_len; int ret = ctx->bio_read(ctx->private_data, ctx->read_buffer + ctx->read_buffer_len, to_read); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { ctx->want_read = 1; return HTTP2_ERR_EAGAIN; } ctx->status = HTTP2_ERR_IO; return HTTP2_ERR_IO; } if (ret == 0) { ctx->status = HTTP2_ERR_EOF; return HTTP2_ERR_EOF; } ctx->read_buffer_len += ret; if (ctx->read_buffer_len < HTTP2_CONNECTION_PREFACE_LEN) { ctx->want_read = 1; return HTTP2_ERR_EAGAIN; } } /* Verify preface */ if (memcmp(ctx->read_buffer, HTTP2_CONNECTION_PREFACE, HTTP2_CONNECTION_PREFACE_LEN) != 0) { /* Check if it looks like HTTP/1.1 */ if (ctx->read_buffer_len >= 4 && (memcmp(ctx->read_buffer, "GET ", 4) == 0 || memcmp(ctx->read_buffer, "POST ", 5) == 0 || memcmp(ctx->read_buffer, "HEAD ", 5) == 0 || memcmp(ctx->read_buffer, "PUT ", 4) == 0 || memcmp(ctx->read_buffer, "DELETE ", 7) == 0 || memcmp(ctx->read_buffer, "OPTIONS ", 8) == 0 || memcmp(ctx->read_buffer, "PATCH ", 6) == 0 || memcmp(ctx->read_buffer, "CONNECT ", 8) == 0 || memcmp(ctx->read_buffer, "TRACE ", 6) == 0)) { ctx->status = HTTP2_ERR_HTTP1; return HTTP2_ERR_HTTP1; } ctx->status = HTTP2_ERR_PROTOCOL; return HTTP2_ERR_PROTOCOL; } /* Preface verified, remove it from buffer */ ctx->read_buffer_len -= HTTP2_CONNECTION_PREFACE_LEN; if (ctx->read_buffer_len > 0) { memmove(ctx->read_buffer, ctx->read_buffer + HTTP2_CONNECTION_PREFACE_LEN, ctx->read_buffer_len); } ctx->preface_received = 1; return 0; } static int _http2_process_frames(struct http2_ctx *ctx) { if (!ctx) { return -1; } ctx->want_read = 0; /* Server: verify connection preface */ int ret = http2_verify_connection_preface(ctx); if (ret != 0) { return ret; } while (1) { /* Try to read frame header */ if (ctx->read_buffer_len < HTTP2_FRAME_HEADER_SIZE) { ret = ctx->bio_read(ctx->private_data, ctx->read_buffer + ctx->read_buffer_len, HTTP2_FRAME_HEADER_SIZE - ctx->read_buffer_len); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { /* Normal for async I/O - just wait for more data */ ctx->want_read = 1; return HTTP2_ERR_EAGAIN; } /* Real error */ ctx->status = HTTP2_ERR_IO; return HTTP2_ERR_IO; } if (ret == 0) { /* Connection closed */ ctx->status = HTTP2_ERR_EOF; return HTTP2_ERR_EOF; } ctx->read_buffer_len += ret; if (ctx->read_buffer_len < HTTP2_FRAME_HEADER_SIZE) { /* Need more data */ ctx->want_read = 1; return 0; } } /* Parse frame header */ uint32_t length = read_uint24(ctx->read_buffer); uint8_t type = ctx->read_buffer[3]; uint8_t flags = ctx->read_buffer[4]; uint32_t stream_id = read_uint32(ctx->read_buffer + 5) & 0x7FFFFFFF; /* Validate frame length */ if (length > ctx->peer_max_frame_size || length > INT_MAX) { ctx->status = HTTP2_ERR_PROTOCOL; return HTTP2_ERR_PROTOCOL; } /* Read frame payload */ if (ctx->read_buffer_len < (int)(HTTP2_FRAME_HEADER_SIZE + length)) { int to_read = HTTP2_FRAME_HEADER_SIZE + length - ctx->read_buffer_len; ret = ctx->bio_read(ctx->private_data, ctx->read_buffer + ctx->read_buffer_len, to_read); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { /* Normal for async I/O - just wait for more data */ ctx->want_read = 1; return HTTP2_ERR_EAGAIN; } /* Real error */ ctx->status = HTTP2_ERR_IO; return HTTP2_ERR_IO; } if (ret == 0) { /* Connection closed */ ctx->status = HTTP2_ERR_EOF; return HTTP2_ERR_EOF; } ctx->read_buffer_len += ret; if (ctx->read_buffer_len < (int)(HTTP2_FRAME_HEADER_SIZE + length)) { /* Need more data */ ctx->want_read = 1; return 0; } } /* Process frame */ const uint8_t *payload = ctx->read_buffer + HTTP2_FRAME_HEADER_SIZE; switch (type) { case HTTP2_FRAME_DATA: _http2_process_data_frame(ctx, stream_id, payload, length, flags); break; case HTTP2_FRAME_HEADERS: _http2_process_headers_frame(ctx, stream_id, payload, length, flags, HTTP2_FRAME_HEADERS); break; case HTTP2_FRAME_CONTINUATION: /* CONTINUATION frames continue a HEADERS frame */ _http2_process_headers_frame(ctx, stream_id, payload, length, flags, HTTP2_FRAME_CONTINUATION); break; case HTTP2_FRAME_SETTINGS: _http2_process_settings_frame(ctx, payload, length, flags); break; case HTTP2_FRAME_WINDOW_UPDATE: _http2_process_window_update_frame(ctx, stream_id, payload, length); break; case HTTP2_FRAME_PING: /* Echo PING */ if (!(flags & HTTP2_FLAG_ACK)) { uint8_t pong[HTTP2_FRAME_HEADER_SIZE + 8]; _http2_write_frame_header(pong, 8, HTTP2_FRAME_PING, HTTP2_FLAG_ACK, 0); memcpy(pong + HTTP2_FRAME_HEADER_SIZE, payload, 8); _http2_send_frame(ctx, pong, sizeof(pong)); } break; case HTTP2_FRAME_RST_STREAM: if (length != 4) { _http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0); ctx->status = HTTP2_ERR_PROTOCOL; return -1; } /* Close the stream */ { struct http2_stream *rst_stream = _http2_find_stream(ctx, stream_id); if (rst_stream) { rst_stream->state = HTTP2_STREAM_CLOSED; } } break; case HTTP2_FRAME_GOAWAY: if (length < 8) { ctx->status = HTTP2_ERR_PROTOCOL; return -1; } ctx->status = HTTP2_ERR_EOF; break; default: /* Ignore unknown frames */ break; } /* Move remaining data to beginning of buffer */ int frame_size = HTTP2_FRAME_HEADER_SIZE + length; if (ctx->read_buffer_len > frame_size) { memmove(ctx->read_buffer, ctx->read_buffer + frame_size, ctx->read_buffer_len - frame_size); ctx->read_buffer_len -= frame_size; } else { ctx->read_buffer_len = 0; } } return 0; } /* Reference counting functions */ struct http2_ctx *http2_ctx_get(struct http2_ctx *ctx) { if (!ctx) { return NULL; } __sync_add_and_fetch(&ctx->refcount, 1); return ctx; } void http2_ctx_put(struct http2_ctx *ctx) { if (!ctx) { return; } int refcnt = __sync_sub_and_fetch(&ctx->refcount, 1); if (refcnt > 0) { return; /* Still has references */ } if (refcnt < 0) { BUG("http2_ctx_put: negative reference count %d", refcnt); return; } /* Reference count reached zero, free the context */ pthread_mutex_lock(&ctx->mutex); /* Free all streams - each stream will call http2_stream_put */ struct http2_stream *stream, *tmp; list_for_each_entry_safe(stream, tmp, &ctx->streams, node) { _http2_remove_stream(stream, 1); } hpack_free_context(&ctx->encoder); hpack_free_context(&ctx->decoder); /* Free pending write buffer */ if (ctx->pending_write_buffer) { free(ctx->pending_write_buffer); } if (ctx->server) { free(ctx->server); } pthread_mutex_unlock(&ctx->mutex); pthread_mutex_destroy(&ctx->mutex); free(ctx); } void http2_ctx_close(struct http2_ctx *ctx) { struct list_head streams_to_free; if (!ctx) { return; } pthread_mutex_lock(&ctx->mutex); /* Detach all streams from context */ INIT_LIST_HEAD(&streams_to_free); list_splice_init(&ctx->streams, &streams_to_free); pthread_mutex_unlock(&ctx->mutex); /* Now free streams outside the lock - just break circular references */ struct http2_stream *stream, *tmp; list_for_each_entry_safe(stream, tmp, &streams_to_free, node) { _http2_remove_stream(stream, 1); } /* release context reference held by caller */ http2_ctx_put(ctx); } struct http2_stream *http2_stream_get(struct http2_stream *stream) { if (!stream) { return NULL; } __sync_add_and_fetch(&stream->refcount, 1); return stream; } void http2_stream_put(struct http2_stream *stream) { if (!stream) { return; } int refcnt = __sync_sub_and_fetch(&stream->refcount, 1); if (refcnt > 0) { return; /* Still has references */ } if (refcnt < 0) { BUG("http2_stream_put: negative reference count %d", refcnt); return; } if (!list_empty(&stream->node)) { _http2_remove_stream(stream, 0); } _http2_free_headers(stream); free(stream->body_buffer); free(stream); } static void _http2_ctx_init_common(struct http2_ctx *ctx, const struct http2_ctx_init_params *params) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&ctx->mutex, &attr); pthread_mutexattr_destroy(&attr); ctx->refcount = 1; /* Initial reference count */ ctx->is_client = params->is_client; ctx->server = strdup(params->server ? params->server : ""); ctx->bio_read = params->bio_read; ctx->bio_write = params->bio_write; ctx->private_data = params->private_data; ctx->next_stream_id = params->next_stream_id; ctx->connection_window_size = HTTP2_DEFAULT_WINDOW_SIZE; ctx->peer_max_frame_size = HTTP2_DEFAULT_MAX_FRAME_SIZE; ctx->peer_initial_window_size = HTTP2_DEFAULT_WINDOW_SIZE; ctx->active_streams = 0; /* Initialize settings with defaults or provided values */ if (params->settings) { memcpy(&ctx->settings, params->settings, sizeof(struct http2_settings)); } /* Initialize I/O state */ ctx->want_read = 0; ctx->want_write = 0; ctx->pending_write_buffer = NULL; ctx->pending_write_len = 0; ctx->pending_write_capacity = 0; hpack_init_context(&ctx->encoder); hpack_init_context(&ctx->decoder); hash_init(ctx->stream_map); INIT_LIST_HEAD(&ctx->streams); } static struct http2_ctx *http2_ctx_new(int is_client, const char *server, http2_bio_read_fn bio_read, http2_bio_write_fn bio_write, void *private_data, const struct http2_settings *settings) { struct http2_ctx *ctx = zalloc(1, sizeof(*ctx)); if (!ctx) { return NULL; } struct http2_ctx_init_params params = {.server = server, .bio_read = bio_read, .bio_write = bio_write, .private_data = private_data, .settings = settings, .is_client = is_client, .next_stream_id = is_client ? 1 : 2}; _http2_ctx_init_common(ctx, ¶ms); if (is_client) { /* Send connection preface - may return EAGAIN, will be buffered */ _http2_send_frame(ctx, (const uint8_t *)HTTP2_CONNECTION_PREFACE, HTTP2_CONNECTION_PREFACE_LEN); } /* Send initial SETTINGS - may return EAGAIN, will be buffered */ _http2_send_settings(ctx, 0); return ctx; } struct http2_ctx *http2_ctx_client_new(const char *server, http2_bio_read_fn bio_read, http2_bio_write_fn bio_write, void *private_data, const struct http2_settings *settings) { if (!server || !bio_read || !bio_write) { return NULL; } return http2_ctx_new(1, server, bio_read, bio_write, private_data, settings); } struct http2_ctx *http2_ctx_server_new(const char *server, http2_bio_read_fn bio_read, http2_bio_write_fn bio_write, void *private_data, const struct http2_settings *settings) { if (!server || !bio_read || !bio_write) { return NULL; } return http2_ctx_new(0, server, bio_read, bio_write, private_data, settings); } int http2_ctx_handshake(struct http2_ctx *ctx) { if (!ctx) { return -1; } pthread_mutex_lock(&ctx->mutex); if (ctx->handshake_complete) { pthread_mutex_unlock(&ctx->mutex); return 1; } /* Try to flush any pending writes (e.g., connection preface, SETTINGS) */ if (ctx->pending_write_len > 0) { /* Trigger flush by calling send_frame with empty data */ uint8_t dummy = 0; _http2_send_frame(ctx, &dummy, 0); } /* Process incoming frames */ int ret = _http2_process_frames(ctx); /* Handshake is complete after receiving SETTINGS */ if (ctx->settings_received) { ctx->handshake_complete = 1; pthread_mutex_unlock(&ctx->mutex); return 1; } if (ret == HTTP2_ERR_EAGAIN) { pthread_mutex_unlock(&ctx->mutex); return 0; /* In progress */ } pthread_mutex_unlock(&ctx->mutex); return ret; } struct http2_stream *http2_ctx_accept_stream(struct http2_ctx *ctx) { if (!ctx) { return NULL; } pthread_mutex_lock(&ctx->mutex); /* Try to flush any pending writes first */ if (ctx->pending_write_len > 0) { uint8_t dummy = 0; _http2_send_frame(ctx, &dummy, 0); } /* Process frames to get new streams */ _http2_process_frames(ctx); /* Find a stream that was initiated by peer */ struct http2_stream *stream; list_for_each_entry(stream, &ctx->streams, node) { if ((ctx->is_client && (stream->stream_id % 2) == 0) || (!ctx->is_client && (stream->stream_id % 2) == 1)) { if (!stream->accepted && !list_empty(&stream->header_list.list) && !stream->end_stream_sent) { stream->accepted = 1; pthread_mutex_unlock(&ctx->mutex); if (stream) { /* take ownership */ http2_stream_get(stream); } return stream; } } } pthread_mutex_unlock(&ctx->mutex); return NULL; } static int _http2_ctx_io_process(struct http2_ctx *ctx) { /* Try to flush any pending writes first */ if (ctx->pending_write_len > 0) { uint8_t dummy = 0; _http2_send_frame(ctx, &dummy, 0); } /* Process frames */ return _http2_process_frames(ctx); } static int _http2_ctx_check_new_streams(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items, int *count) { if (ctx->is_client || *count >= max_items) { return 0; } struct http2_stream *stream; int has_new_stream = 0; list_for_each_entry(stream, &ctx->streams, node) { /* Server accepts odd stream IDs (client-initiated) */ /* Stream is ready to accept when it has received headers */ if ((stream->stream_id % 2) == 1 && !stream->accepted && !list_empty(&stream->header_list.list) && !stream->end_stream_sent) { has_new_stream = 1; break; } } if (has_new_stream) { /* Return server context item (stream = NULL) to indicate new connection */ items[*count].stream = NULL; items[*count].readable = 1; items[*count].writable = 0; (*count)++; return 1; } return 0; } static void _http2_ctx_collect_ready_streams(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items, int *count, int check_writable) { struct http2_stream *stream, *tmp; struct list_head ready_list; INIT_LIST_HEAD(&ready_list); list_for_each_entry_safe(stream, tmp, &ctx->streams, node) { /* Only return streams that have been accepted */ if (!stream->accepted) { continue; } /* Stream is readable if: * 1. Has unread body data in buffer, OR * 2. Stream has ended (all data including headers received) */ int has_body_data = stream->body_buffer_len > stream->body_read_offset; int stream_ended = (stream->end_stream_received || stream->state == HTTP2_STREAM_CLOSED) && !stream->end_stream_read_handled; int readable = has_body_data || stream_ended; int writable = stream->state == HTTP2_STREAM_OPEN || stream->state == HTTP2_STREAM_HALF_CLOSED_REMOTE; if (readable || (check_writable && writable)) { if (*count < max_items) { items[*count].stream = http2_stream_get(stream); items[*count].readable = readable; items[*count].writable = writable; (*count)++; /* Move to ready list */ list_move_tail(&stream->node, &ready_list); } } } /* Append ready list to the end of ctx->streams */ list_splice_tail(&ready_list, &ctx->streams); } static int _http2_ctx_poll(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items, int *ret_count, int check_writable) { pthread_mutex_lock(&ctx->mutex); if (ret_count) { *ret_count = 0; } int ret = _http2_ctx_io_process(ctx); if (items == NULL || max_items <= 0) { pthread_mutex_unlock(&ctx->mutex); return ret; } /* Note: We continue even if http2_process_frames returns error (like EOF), because we might have received data that made streams readable. We will return the error at the end if no streams are ready. */ int count = 0; _http2_ctx_check_new_streams(ctx, items, max_items, &count); _http2_ctx_collect_ready_streams(ctx, items, max_items, &count, check_writable); if (ret_count) { *ret_count = count; } /* If we have an error, return it even if there ready items. BUT we must release references collected in items before returning. */ if (ret < 0 && ret != HTTP2_ERR_EAGAIN) { int i; for (i = 0; i < count; i++) { if (items[i].stream) { http2_stream_put(items[i].stream); items[i].stream = NULL; } } if (ret_count) { *ret_count = 0; } pthread_mutex_unlock(&ctx->mutex); return ret; } pthread_mutex_unlock(&ctx->mutex); if (count > 0) { return 0; } if (ctx->status < 0) { return ctx->status; } return 0; } int http2_ctx_poll(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items, int *ret_count) { if (!ctx) { return -1; } if (items != NULL && (max_items <= 0 || !ret_count)) { return -1; } return _http2_ctx_poll(ctx, items, max_items, ret_count, 1); } int http2_ctx_poll_readable(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items, int *ret_count) { if (!ctx) { return -1; } if (items != NULL && (max_items <= 0 || !ret_count)) { return -1; } return _http2_ctx_poll(ctx, items, max_items, ret_count, 0); } struct http2_stream *http2_stream_new(struct http2_ctx *ctx) { if (!ctx) { return NULL; } pthread_mutex_lock(&ctx->mutex); int stream_id = ctx->next_stream_id; ctx->next_stream_id += 2; struct http2_stream *stream = _http2_create_stream(ctx, stream_id); if (stream) { /* take ownership */ http2_stream_get(stream); } pthread_mutex_unlock(&ctx->mutex); return stream; } static int http2_send_rst_stream(struct http2_ctx *ctx, uint32_t stream_id, uint32_t error_code) { uint8_t frame[HTTP2_FRAME_HEADER_SIZE + 4]; _http2_write_frame_header(frame, 4, HTTP2_FRAME_RST_STREAM, 0, stream_id); write_uint32(frame + HTTP2_FRAME_HEADER_SIZE, error_code); return _http2_send_frame(ctx, frame, sizeof(frame)); } void http2_stream_close(struct http2_stream *stream) { if (stream == NULL) { return; } http2_stream_get(stream); /* Ensure stream survives during close */ /* Mark stream as closed */ stream->state = HTTP2_STREAM_CLOSED; stream->ex_data = NULL; struct http2_ctx *ctx = stream->ctx; if (ctx) { pthread_mutex_lock(&ctx->mutex); /* Send RST_STREAM to close the stream */ http2_send_rst_stream(ctx, stream->stream_id, 0); /* NO_ERROR */ pthread_mutex_unlock(&ctx->mutex); _http2_remove_stream(stream, 1); } http2_stream_put(stream); http2_stream_put(stream); } int http2_stream_get_id(struct http2_stream *stream) { if (!stream) { return -1; } return stream->stream_id; } int http2_stream_set_request(struct http2_stream *stream, const char *method, const char *path, const char *scheme, const struct http2_header_pair *headers) { if (!stream || !method || !path) { return -1; } struct http2_ctx *ctx = stream->ctx; if (!ctx) { return -1; } pthread_mutex_lock(&ctx->mutex); /* Clear old headers */ _http2_free_headers(stream); /* Add pseudo-headers */ _http2_stream_add_header(stream, ":method", method); _http2_stream_add_header(stream, ":path", path); _http2_stream_add_header(stream, ":scheme", scheme ? scheme : "https"); _http2_stream_add_header(stream, ":authority", ctx->server ? ctx->server : "localhost"); /* Add additional headers from array */ if (headers) { for (int i = 0; headers[i].name != NULL; i++) { if (headers[i].value == NULL) { continue; } if (headers[i].name == NULL) { continue; } _http2_stream_add_header(stream, headers[i].name, headers[i].value); } } /* Send HEADERS frame with END_STREAM for GET/HEAD requests (no body) */ int end_stream = (strcmp(method, "GET") == 0 || strcmp(method, "HEAD") == 0) ? 1 : 0; int ret = _http2_send_headers_frame(ctx, stream->stream_id, stream, end_stream, 1); if (end_stream) { stream->end_stream_sent = 1; } if (stream->state == HTTP2_STREAM_IDLE) { stream->state = HTTP2_STREAM_OPEN; } pthread_mutex_unlock(&ctx->mutex); return ret; } int http2_stream_set_response(struct http2_stream *stream, int status, const struct http2_header_pair *headers, int header_count) { if (!stream || status < 100 || status > 599 || header_count < 0 || (header_count > 0 && !headers)) { return -1; } struct http2_ctx *ctx = stream->ctx; if (!ctx) { return -1; } pthread_mutex_lock(&ctx->mutex); /* Clear old headers */ _http2_free_headers(stream); /* Add :status pseudo-header */ char status_str[16]; snprintf(status_str, sizeof(status_str), "%d", status); _http2_stream_add_header(stream, ":status", status_str); /* Add additional headers from array */ if (headers && header_count > 0) { for (int i = 0; i < header_count; i++) { if (headers[i].name == NULL || headers[i].value == NULL) { continue; } _http2_stream_add_header(stream, headers[i].name, headers[i].value); } } /* Send HEADERS frame */ int ret = _http2_send_headers_frame(ctx, stream->stream_id, stream, 0, 1); pthread_mutex_unlock(&ctx->mutex); return ret; } const char *http2_stream_get_method(struct http2_stream *stream) { if (!stream) { return NULL; } struct http2_ctx *ctx = stream->ctx; if (!ctx) { return NULL; } pthread_mutex_lock(&ctx->mutex); const char *method = _http2_stream_get_header_value(stream, ":method"); pthread_mutex_unlock(&ctx->mutex); return method; } const char *http2_stream_get_path(struct http2_stream *stream) { if (!stream) { return NULL; } struct http2_ctx *ctx = stream->ctx; if (!ctx) { return NULL; } pthread_mutex_lock(&ctx->mutex); const char *path = _http2_stream_get_header_value(stream, ":path"); pthread_mutex_unlock(&ctx->mutex); return path; } int http2_stream_get_status(struct http2_stream *stream) { if (!stream) { return -1; } struct http2_ctx *ctx = stream->ctx; if (!ctx) { return -1; } pthread_mutex_lock(&ctx->mutex); const char *status_str = _http2_stream_get_header_value(stream, ":status"); int status = status_str ? atoi(status_str) : -1; pthread_mutex_unlock(&ctx->mutex); return status; } const char *http2_stream_get_header(struct http2_stream *stream, const char *name) { if (!stream || !name) { return NULL; } struct http2_ctx *ctx = stream->ctx; if (!ctx) { return NULL; } pthread_mutex_lock(&ctx->mutex); const char *value = _http2_stream_get_header_value(stream, name); pthread_mutex_unlock(&ctx->mutex); return value; } int http2_stream_write_body(struct http2_stream *stream, const uint8_t *data, int len, int end_stream) { if (!stream || len <= 0 || !data) { return -1; } struct http2_ctx *ctx = stream->ctx; if (!ctx) { return -1; } pthread_mutex_lock(&ctx->mutex); int total_sent = 0; while (len > 0) { /* Check flow control */ if (stream->window_size <= 0 || ctx->connection_window_size <= 0) { if (total_sent > 0) { break; /* Partial send */ } pthread_mutex_unlock(&ctx->mutex); errno = EAGAIN; return -1; } int to_send = len; if (to_send > stream->window_size) { to_send = stream->window_size; } if (to_send > ctx->connection_window_size) { to_send = ctx->connection_window_size; } if ((uint32_t)to_send > (uint32_t)ctx->peer_max_frame_size) { to_send = ctx->peer_max_frame_size; } if (to_send <= 0) { if (total_sent > 0) { break; } pthread_mutex_unlock(&ctx->mutex); errno = EAGAIN; return -1; } int ret = _http2_send_data_frame(ctx, stream->stream_id, data + total_sent, to_send, end_stream && to_send == len); if (ret < 0) { if (total_sent > 0) { break; /* Partial send */ } pthread_mutex_unlock(&ctx->mutex); return ret; } total_sent += ret; len -= ret; stream->window_size -= ret; ctx->connection_window_size -= ret; if (end_stream && len == 0) { stream->end_stream_sent = 1; if (stream->state == HTTP2_STREAM_OPEN) { stream->state = HTTP2_STREAM_HALF_CLOSED_LOCAL; } else if (stream->state == HTTP2_STREAM_HALF_CLOSED_REMOTE) { stream->state = HTTP2_STREAM_CLOSED; } } } pthread_mutex_unlock(&ctx->mutex); return total_sent; } /* Helper function to decompress gzip/deflate data */ static int http2_decompress_data(const uint8_t *compressed, int compressed_len, uint8_t **decompressed, int *decompressed_len, int is_gzip) { #ifdef WITH_ZLIB z_stream strm; int ret; int window_bits = is_gzip ? (15 + 16) : 15; /* 15+16 for gzip, 15 for deflate */ const int MAX_DECOMPRESSED_SIZE = 1 * 1024 * 1024; /* 10MB limit to prevent DOS */ /* Allocate initial output buffer */ int out_size = safe_buffer_size(compressed_len, 4); if (out_size < 0 || out_size < 8192) { out_size = 8192; } if (out_size > MAX_DECOMPRESSED_SIZE) { out_size = MAX_DECOMPRESSED_SIZE; } uint8_t *out_buf = zalloc(1, out_size); if (!out_buf) { return -1; } /* Initialize zlib */ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = compressed_len; strm.next_in = (Bytef *)compressed; ret = inflateInit2(&strm, window_bits); if (ret != Z_OK) { free(out_buf); return -1; } /* Decompress */ int total_out = 0; do { /* Ensure we have space in output buffer */ if (total_out + 4096 > out_size) { int new_size = safe_buffer_size(out_size, 2); if (new_size < 0 || new_size > MAX_DECOMPRESSED_SIZE) { inflateEnd(&strm); free(out_buf); return -1; } uint8_t *new_buf = realloc(out_buf, new_size); if (!new_buf) { inflateEnd(&strm); free(out_buf); return -1; } out_buf = new_buf; out_size = new_size; } strm.avail_out = out_size - total_out; strm.next_out = out_buf + total_out; ret = inflate(&strm, Z_NO_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END) { inflateEnd(&strm); free(out_buf); return -1; } total_out = strm.total_out; } while (ret != Z_STREAM_END && strm.avail_in > 0); inflateEnd(&strm); *decompressed = out_buf; *decompressed_len = total_out; return 0; #else return -1; #endif } static int http2_try_decompress_body(struct http2_stream *stream) { if (!stream || stream->body_decompressed || stream->body_buffer_len == 0) { return 0; } /* Only decompress when the stream is fully received or connection is closed */ if (!stream->end_stream_received && stream->ctx && stream->ctx->status >= 0) { return 0; } const char *content_encoding = _http2_stream_get_header_value(stream, "content-encoding"); int is_gzip = 0; int should_decompress = 0; if (content_encoding) { is_gzip = (strcasecmp(content_encoding, "gzip") == 0); int is_deflate = (strcasecmp(content_encoding, "deflate") == 0); should_decompress = (is_gzip || is_deflate); } else if (stream->body_buffer_len > 2) { /* Fallback: check for gzip magic number (0x1f 0x8b) */ if (stream->body_buffer[0] == 0x1f && stream->body_buffer[1] == 0x8b) { is_gzip = 1; should_decompress = 1; } } if (should_decompress) { uint8_t *decompressed = NULL; int decompressed_len = 0; if (http2_decompress_data(stream->body_buffer, stream->body_buffer_len, &decompressed, &decompressed_len, is_gzip) == 0) { /* Replace compressed buffer with decompressed data */ free(stream->body_buffer); stream->body_buffer = decompressed; stream->body_buffer_len = decompressed_len; stream->body_buffer_size = decompressed_len; stream->body_decompressed = 1; return 1; /* Decompression successful */ } else { /* Decompression failed, set an error flag or log */ /* For now, leave body_decompressed = 0, and let read_body handle error */ return -1; /* Indicate failure */ } } return 0; } int http2_stream_read_body(struct http2_stream *stream, uint8_t *data, int len) { if (!stream || len <= 0 || !data) { return -1; } struct http2_ctx *ctx = stream->ctx; if (ctx) { pthread_mutex_lock(&ctx->mutex); } /* NOTE: We do NOT call http2_process_frames here! * The caller should use http2_ctx_poll to process frames for all streams. * This function only reads from the stream's buffer. */ /* Try to decompress if needed */ int decompress_ret = http2_try_decompress_body(stream); if (decompress_ret < 0) { /* Decompression failed, return error */ if (ctx) { pthread_mutex_unlock(&ctx->mutex); } errno = EINVAL; /* Invalid data */ return -1; } /* If content is compressed but not yet decompressed (because stream not ended), we must not return raw data. */ const char *content_encoding = _http2_stream_get_header_value(stream, "content-encoding"); if (content_encoding && !stream->body_decompressed) { /* Check if it's a compression format we handle */ if (strcasecmp(content_encoding, "gzip") == 0 || strcasecmp(content_encoding, "deflate") == 0) { /* If stream not ended and connection is healthy, return EAGAIN */ if (!stream->end_stream_received && (!ctx || ctx->status >= 0)) { if (ctx) { pthread_mutex_unlock(&ctx->mutex); } errno = EAGAIN; return -1; } /* If stream ended but decompression failed earlier, error already returned */ } } int available = stream->body_buffer_len - stream->body_read_offset; if (available <= 0) { if (ctx) { pthread_mutex_unlock(&ctx->mutex); } /* If stream ended or connection has error, return 0 (EOF) */ if (stream->end_stream_received || stream->state == HTTP2_STREAM_CLOSED || (!ctx || ctx->status < 0)) { stream->end_stream_read_handled = 1; return 0; } /* No data available yet, return EAGAIN */ errno = EAGAIN; return -1; } int to_read = available < len ? available : len; memcpy(data, stream->body_buffer + stream->body_read_offset, to_read); stream->body_read_offset += to_read; if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return to_read; } int http2_stream_body_available(struct http2_stream *stream) { if (!stream) { return 0; } struct http2_ctx *ctx = stream->ctx; if (ctx) { pthread_mutex_lock(&ctx->mutex); } /* Try to decompress if needed */ http2_try_decompress_body(stream); /* If content is compressed but not yet decompressed, pretend no data available */ const char *content_encoding = _http2_stream_get_header_value(stream, "content-encoding"); if (content_encoding && !stream->body_decompressed) { if (strcasecmp(content_encoding, "gzip") == 0 || strcasecmp(content_encoding, "deflate") == 0) { if (!stream->end_stream_received && (!ctx || ctx->status >= 0)) { if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return 0; } } } int available = stream->body_buffer_len - stream->body_read_offset; if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return available > 0 ? 1 : 0; } int http2_stream_is_end(struct http2_stream *stream) { if (!stream) { return 1; } struct http2_ctx *ctx = stream->ctx; if (ctx) { pthread_mutex_lock(&ctx->mutex); } /* Try to decompress if needed - this might change body_buffer_len */ http2_try_decompress_body(stream); int is_end = (stream->end_stream_received || stream->state == HTTP2_STREAM_CLOSED) && (stream->body_read_offset >= stream->body_buffer_len); /* If connection is closed/error, and we have read all buffered data, consider stream ended */ if (!is_end && (!ctx || ctx->status < 0) && stream->body_read_offset >= stream->body_buffer_len) { is_end = 1; } if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return is_end; } void http2_stream_set_ex_data(struct http2_stream *stream, void *data) { if (!stream) { return; } struct http2_ctx *ctx = stream->ctx; if (ctx) { pthread_mutex_lock(&ctx->mutex); } stream->ex_data = data; if (ctx) { pthread_mutex_unlock(&ctx->mutex); } } void *http2_stream_get_ex_data(struct http2_stream *stream) { if (!stream) { return NULL; } struct http2_ctx *ctx = stream->ctx; if (ctx) { pthread_mutex_lock(&ctx->mutex); } void *data = stream->ex_data; if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return data; } int http2_ctx_want_read(struct http2_ctx *ctx) { if (!ctx) { return 0; } pthread_mutex_lock(&ctx->mutex); int want_read = ctx->want_read; pthread_mutex_unlock(&ctx->mutex); return want_read; } int http2_ctx_want_write(struct http2_ctx *ctx) { if (!ctx) { return 0; } pthread_mutex_lock(&ctx->mutex); int want_write = ctx->want_write; pthread_mutex_unlock(&ctx->mutex); return want_write; } int http2_ctx_is_closed(struct http2_ctx *ctx) { if (!ctx) { return 1; } pthread_mutex_lock(&ctx->mutex); int is_closed = (ctx->status < 0); pthread_mutex_unlock(&ctx->mutex); return is_closed; } char *http2_stream_get_query_param(struct http2_stream *stream, const char *name) { const char *path = NULL; const char *q = NULL; const char *val_start = NULL; int name_len = 0; char *ret = NULL; const int MAX_VAL_LEN = 4096; /* Limit to prevent excessive memory use */ if (stream == NULL || name == NULL) { return NULL; } struct http2_ctx *ctx = stream->ctx; if (ctx) { pthread_mutex_lock(&ctx->mutex); } path = _http2_stream_get_header_value(stream, ":path"); if (path == NULL) { if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return NULL; } q = strstr(path, "?"); if (q == NULL) { if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return NULL; } q++; name_len = strlen(name); while (*q) { if (strncmp(q, name, name_len) == 0 && q[name_len] == '=') { val_start = q + name_len + 1; break; } q = strchr(q, '&'); if (q == NULL) { break; } q++; } if (val_start) { const char *end = strchr(val_start, '&'); size_t val_len = end ? (size_t)(end - val_start) : strlen(val_start); if (val_len > (size_t)MAX_VAL_LEN) { if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return NULL; /* Too long, reject */ } char *encoded_val = strndup(val_start, val_len); if (encoded_val) { size_t decoded_len = val_len * 2 + 1; // Estimate char *decoded_val = zalloc(1, decoded_len); if (decoded_val) { int ret_len = urldecode(decoded_val, decoded_len, encoded_val); if (ret_len >= 0 && (size_t)ret_len < decoded_len) { ret = decoded_val; } else { free(decoded_val); } } free(encoded_val); } } if (ctx) { pthread_mutex_unlock(&ctx->mutex); } return ret; } ================================================ FILE: src/http_parse/http3_parse.c ================================================ /************************************************************************* * * 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 . */ #include "http3_parse.h" #include "http_parse.h" #include "qpack.h" #include #define HTTP3_HEADER_FRAME 1 #define HTTP3_DATA_FRAME 0 static int _quicvarint_encode(uint64_t value, uint8_t *buffer, int buffer_size) { if (value <= 63) { if (buffer_size < 1) { return -1; } buffer[0] = (uint8_t)value; return 1; } else if (value <= 16383) { if (buffer_size < 2) { return -1; } buffer[0] = (uint8_t)((value >> 8) | 0x40); buffer[1] = (uint8_t)value; return 2; } else if (value <= 1073741823) { if (buffer_size < 4) { return -1; } buffer[0] = (uint8_t)((value >> 24) | 0x80); buffer[1] = (uint8_t)(value >> 16); buffer[2] = (uint8_t)(value >> 8); buffer[3] = (uint8_t)value; return 4; } else { if (buffer_size < 8) { return -1; } buffer[0] = (uint8_t)((value >> 56) | 0xC0); buffer[1] = (uint8_t)(value >> 48); buffer[2] = (uint8_t)(value >> 40); buffer[3] = (uint8_t)(value >> 32); buffer[4] = (uint8_t)(value >> 24); buffer[5] = (uint8_t)(value >> 16); buffer[6] = (uint8_t)(value >> 8); buffer[7] = (uint8_t)value; return 8; } } static int _qpack_build_header(const char *name, const char *value, uint8_t *buffer, int buffer_size) { int offset = 0; int offset_ret = 0; int name_len = strlen(name); int value_len = strlen(value); if (buffer_size - offset < 2) { return -1; } if (name_len < 7) { buffer[offset++] = 0x20 | name_len; } else { buffer[offset++] = 0x20 | 7; buffer[offset++] = name_len - 7; } if (buffer_size - offset < name_len) { return -1; } memcpy(buffer + offset, name, name_len); offset += name_len; if (buffer_size - offset < 2) { return -1; } offset_ret = _quicvarint_encode(value_len, buffer + offset, buffer_size - offset); if (offset_ret < 0) { return -1; } offset += offset_ret; if (buffer_size - offset < value_len) { return -1; } memcpy(buffer + offset, value, value_len); offset += value_len; return offset; } static int _http3_build_headers_payload(struct http_head *http_head, uint8_t *buffer, int buffer_len) { int offset = 0; int offset_ret = 0; struct http_head_fields *fields = NULL; struct http_params *params = NULL; /* Insert count and delta base */ if (buffer_len - offset < 2) { return -1; } buffer[offset++] = 0; buffer[offset++] = 0; if (http_head->head_type == HTTP_HEAD_REQUEST) { char request_path[1024]; char *request_path_buffer = request_path; int request_path_buffer_len = sizeof(request_path); int request_path_len = snprintf(request_path, sizeof(request_path), "%s", http_head->url); if (request_path_len < 0) { return -1; } request_path_buffer += request_path_len; request_path_buffer_len -= request_path_len; int count = 0; list_for_each_entry(params, &http_head->params.list, list) { if (count == 0) { request_path_len = snprintf(request_path_buffer, request_path_buffer_len, "?%s=%s", params->name, params->value); } else { request_path_len = snprintf(request_path_buffer, request_path_buffer_len, "&%s=%s", params->name, params->value); } count++; request_path_buffer += request_path_len; request_path_buffer_len -= request_path_len; if (request_path_buffer_len < 2) { return -3; } } offset_ret = _qpack_build_header(":method", http_method_str(http_head->method), buffer + offset, buffer_len - offset); if (offset_ret < 0) { return -1; } offset += offset_ret; offset_ret = _qpack_build_header(":path", request_path, buffer + offset, buffer_len - offset); if (offset_ret < 0) { return -1; } offset += offset_ret; offset_ret = _qpack_build_header(":scheme", "https", buffer + offset, buffer_len - offset); if (offset_ret < 0) { return -1; } offset += offset_ret; } else if (http_head->head_type == HTTP_HEAD_RESPONSE) { char status_str[12]; snprintf(status_str, sizeof(status_str), "%d", http_head->code); offset_ret = _qpack_build_header(":status", status_str, buffer + offset, buffer_len - offset); if (offset_ret < 0) { return -1; } offset += offset_ret; } list_for_each_entry(fields, &http_head->field_head.list, list) { offset_ret = _qpack_build_header(fields->name, fields->value, buffer + offset, buffer_len - offset); if (offset_ret < 0) { return -1; } offset += offset_ret; } if (http_head->data_len > 0 && http_head->data) { char len_str[12]; snprintf(len_str, sizeof(len_str), "%d", http_head->data_len); offset_ret = _qpack_build_header("content-length", len_str, buffer + offset, buffer_len - offset); if (offset_ret < 0) { return -1; } offset += offset_ret; } return offset; } static int http3_build_body_payload(const uint8_t *body, int body_len, uint8_t *buffer, int buffer_len) { int offset = 0; int offset_ret = 0; offset_ret = _quicvarint_encode(body_len, buffer + offset, buffer_len - offset); if (offset_ret < 0) { return -1; } offset += offset_ret; if (buffer_len - offset < body_len) { return -1; } memcpy(buffer + offset, body, body_len); offset += body_len; return offset; } static int _quicvarint_decode(const uint8_t *buffer, int buffer_len, uint64_t *value) { if ((buffer[0] & 0xC0) == 0x00) { if (buffer_len < 1) { return -1; } *value = buffer[0]; return 1; } else if ((buffer[0] & 0xC0) == 0x40) { if (buffer_len < 2) { return -1; } *value = ((uint64_t)(buffer[0] & 0x3F) << 8) | buffer[1]; return 2; } else if ((buffer[0] & 0xC0) == 0x80) { if (buffer_len < 4) { return -1; } *value = ((uint64_t)(buffer[0] & 0x3F) << 24) | ((uint64_t)buffer[1] << 16) | ((uint64_t)buffer[2] << 8) | buffer[3]; return 4; } else { if (buffer_len < 8) { return -1; } *value = ((uint64_t)(buffer[0] & 0x3F) << 56) | ((uint64_t)buffer[1] << 48) | ((uint64_t)buffer[2] << 40) | ((uint64_t)buffer[3] << 32) | ((uint64_t)buffer[4] << 24) | ((uint64_t)buffer[5] << 16) | ((uint64_t)buffer[6] << 8) | buffer[7]; return 8; } } static int _quic_read_varint(const uint8_t *buffer, int buffer_len, uint64_t *value, int n) { uint64_t i; if (n < 1 || n > 8) { return -2; } if (buffer_len == 0) { return -1; } const uint8_t *p = buffer; i = *p; if (n < 8) { i &= (1 << (uint64_t)n) - 1; } if (i < (uint64_t)(1 << (uint64_t)n) - 1) { *value = i; return 1; } p++; uint64_t m = 0; while (p < buffer + buffer_len) { uint8_t b = *p; i += (uint64_t)(b & 127) << m; if ((b & 128) == 0) { *value = i; return p - buffer + 1; } m += 7; if (m >= 63) { return -1; } p++; } return -1; } static int _quic_read_string(const uint8_t *buffer, int buffer_len, char *str, int max_str_len, size_t *str_len, int n, int huffman) { uint64_t len = 0; int offset = 0; int offset_ret = 0; offset_ret = _quic_read_varint(buffer, buffer_len, &len, n); if (offset_ret < 0) { return -1; } offset += offset_ret; if ((uint64_t)(buffer_len - offset) < len) { return -1; } if ((uint64_t)max_str_len < len) { return -3; } if (huffman) { size_t char_len = 0; if (qpack_huffman_decode(buffer + offset, buffer + offset + len, (uint8_t *)str, max_str_len, &char_len) < 0) { return -1; } str[char_len] = '\0'; *str_len = char_len; } else { memcpy(str, buffer + offset, len); str[len] = '\0'; *str_len = len; } return offset + len; } static int _http3_parse_headers_payload(struct http_head *http_head, const uint8_t *data, int data_len) { int offset = 0; int offset_ret = 0; int insert_count = 0; int delta_base = 0; struct qpack_header_field *header = NULL; const char *name = NULL; const char *value = NULL; uint64_t index = -1; int use_huffman = 0; uint8_t b = 0; size_t str_len = 0; int buffer_left_len = 0; if (data_len < 2) { return -1; } insert_count = data[0]; delta_base = data[1]; if (insert_count != 0 || delta_base != 0) { return -2; } offset += 2; while (offset < data_len) { index = -1; use_huffman = 0; name = NULL; value = NULL; char *buffer_name = NULL; char *buffer_value = NULL; str_len = 0; b = data[offset]; if (b & 0x80) { /* indexed header*/ if ((b & 0x40) == 0) { return -2; } offset_ret = _quic_read_varint(data + offset, data_len - offset, &index, 6); if (offset_ret < 0) { return -1; } offset += offset_ret; header = qpack_get_static_header_field(index); if (header == NULL) { return -2; } name = header->name; value = header->value; } else if (b & 0x40) { /* literal header with indexing */ if ((b & 0x10) == 0) { return -2; } offset_ret = _quic_read_varint(data + offset, data_len - offset, &index, 4); if (offset_ret < 0) { return -1; } offset += offset_ret; header = qpack_get_static_header_field(index); if (header == NULL) { return -2; } name = header->name; b = data[offset]; if ((b & 0x80) > 0) { use_huffman = 1; } buffer_value = (char *)_http_head_buffer_get_end(http_head); buffer_left_len = _http_head_buffer_left_len(http_head); offset_ret = _quic_read_string(data + offset, data_len - offset, buffer_value, buffer_left_len - 1, &str_len, 7, use_huffman); if (offset_ret < 0) { return offset_ret; } offset += offset_ret; buffer_value[str_len] = '\0'; if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { return -3; } value = buffer_value; } else if (b & 0x20) { /* literal header without indexing */ b = data[offset]; if ((b & 0x8) > 0) { use_huffman = 1; } buffer_name = (char *)_http_head_buffer_get_end(http_head); buffer_left_len = _http_head_buffer_left_len(http_head); offset_ret = _quic_read_string(data + offset, data_len - offset, buffer_name, buffer_left_len - 1, &str_len, 3, use_huffman); if (offset_ret < 0) { return offset_ret; } offset += offset_ret; buffer_name[str_len] = '\0'; if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { return -3; } name = buffer_name; b = data[offset]; if ((b & 0x80) > 0) { use_huffman = 1; } buffer_value = (char *)_http_head_buffer_get_end(http_head); buffer_left_len = _http_head_buffer_left_len(http_head); offset_ret = _quic_read_string(data + offset, data_len - offset, buffer_value, buffer_left_len - 1, &str_len, 7, use_huffman); if (offset_ret < 0) { return offset_ret; } offset += offset_ret; buffer_value[str_len] = '\0'; if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { return -3; } value = buffer_value; } else { return -2; } if (http_head_add_fields(http_head, name, value) != 0) { break; } } return 0; } static int _http_head_setup_http3_0_httpcode(struct http_head *http_head) { const char *status = NULL; int status_code = 0; const char *method = NULL; const char *url = NULL; method = http_head_get_fields_value(http_head, ":method"); if (method) { http_head->method = _http_method_parse(method); if (http_head->method == HTTP_METHOD_INVALID) { return -1; } url = http_head_get_fields_value(http_head, ":path"); if (url == NULL) { return -1; } http_head->url = url; http_head->head_type = HTTP_HEAD_REQUEST; if (_http_head_parse_params(http_head, (char *)url, strlen(url) + 1) != 0) { return -1; } return 0; } status = http_head_get_fields_value(http_head, ":status"); if (status == NULL) { return 0; } http_head->head_type = HTTP_HEAD_RESPONSE; status_code = atoi(status); if (status_code < 100 || status_code > 999) { return -1; } http_head->code = status_code; if (status_code == 200) { return 0; } return 0; } int http_head_parse_http3_0(struct http_head *http_head, const uint8_t *data, int data_len) { uint64_t frame_type = 0; uint64_t frame_len = 0; int offset = 0; int offset_ret = 0; http_head->data_len = 0; while (offset < data_len) { offset_ret = _quicvarint_decode((uint8_t *)data + offset, data_len - offset, &frame_type); if (offset_ret < 0) { return offset_ret; } offset += offset_ret; offset_ret = _quicvarint_decode((uint8_t *)data + offset, data_len - offset, &frame_len); if (offset_ret < 0) { return offset_ret; } offset += offset_ret; if (offset >= http_head->buff_size) { return -3; } if ((uint64_t)(data_len - offset) < frame_len) { return -1; } if (frame_type == HTTP3_HEADER_FRAME) { int header_len = 0; header_len = _http3_parse_headers_payload(http_head, data + offset, frame_len); if (header_len < 0) { return header_len; } if (_http_head_setup_http3_0_httpcode(http_head) != 0) { return -1; } } else if (frame_type == HTTP3_DATA_FRAME) { if (http_head->code != 200 && http_head->head_type == HTTP_HEAD_RESPONSE) { if (frame_len > (uint64_t)(http_head->buff_size - http_head->buff_len)) { http_head->code_msg = "Unknow Error"; return 0; } memcpy(http_head->buff + http_head->buff_len, data + offset, frame_len); http_head->code_msg = (const char *)(http_head->buff + http_head->buff_len); http_head->buff_len += frame_len; } else if (frame_len > 0) { if (http_head->data == NULL) { http_head->data = _http_head_buffer_get_end(http_head); } if (_http_head_buffer_append(http_head, data + offset, frame_len) == NULL) { http_head->code_msg = "Receive Buffer Insufficient"; http_head->code = 500; http_head->data_len = 0; http_head->buff_len = 0; return -3; } /* Check buffer space before memcpy */ if ((uint64_t)http_head->buff_len + frame_len > (uint64_t)http_head->buff_size) { http_head->code_msg = "Receive Buffer Insufficient"; http_head->code = 500; http_head->data_len = 0; http_head->buff_len = 0; return -3; } memcpy(http_head->buff + http_head->buff_len, data + offset, frame_len); http_head->data_len += frame_len; } } else { /* skip unknown frame. e.g. GREASE */ offset += frame_len; continue; } offset += frame_len; } if (offset >= http_head->buff_size) { return -3; } http_head->version = "HTTP/3.0"; return offset; } int http_head_serialize_http3_0(struct http_head *http_head, uint8_t *buffer, int buffer_len) { int offset = 0; int offset_ret = 0; uint8_t *header_data = NULL; int header_data_size = 1024; int header_data_len = 0; int result = -1; header_data = malloc(header_data_size); if (!header_data) { goto cleanup; } /* serialize header frame. */ header_data_len = _http3_build_headers_payload(http_head, header_data, header_data_size); if (header_data_len < 0) { goto cleanup; } /* If header_data_len > header_data_size, realloc */ if (header_data_len > header_data_size) { uint8_t *new_header_data = realloc(header_data, header_data_len); if (!new_header_data) { goto cleanup; } header_data = new_header_data; header_data_size = header_data_len; header_data_len = _http3_build_headers_payload(http_head, header_data, header_data_size); if (header_data_len < 0) { goto cleanup; } } /* Frame Type: Header*/ offset_ret = _quicvarint_encode(HTTP3_HEADER_FRAME, buffer + offset, buffer_len - offset); if (offset_ret < 0) { goto cleanup; } offset += offset_ret; /* Header Frame Length */ offset_ret = _quicvarint_encode(header_data_len, buffer + offset, buffer_len - offset); if (offset_ret < 0) { goto cleanup; } offset += offset_ret; if (buffer_len - offset < header_data_len) { goto cleanup; } memcpy(buffer + offset, header_data, header_data_len); offset += header_data_len; /* Frame Type: Data */ if (http_head->data_len > 0 && http_head->data) { /* Data Frame Length */ offset_ret = _quicvarint_encode(HTTP3_DATA_FRAME, buffer + offset, buffer_len - offset); if (offset_ret < 0) { goto cleanup; } offset += offset_ret; offset_ret = http3_build_body_payload(http_head->data, http_head->data_len, buffer + offset, buffer_len - offset); if (offset_ret < 0) { goto cleanup; } offset += offset_ret; } result = offset; cleanup: if (header_data) { free(header_data); } return result; } ================================================ FILE: src/http_parse/http3_parse.h ================================================ /************************************************************************* * * 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 . */ #ifndef _HTTP_PARSE_HTTP3_H_ #define _HTTP_PARSE_HTTP3_H_ #include "http_parse.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ int http_head_parse_http3_0(struct http_head *http_head, const uint8_t *data, int data_len); int http_head_serialize_http3_0(struct http_head *http_head, uint8_t *buffer, int buffer_len); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/http_parse/http_parse.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/http_parse.h" #include "smartdns/util.h" #include "http1_parse.h" #include "http3_parse.h" #include "http_parse.h" #include #include #include #include /* * Returns: * >=0 - success http data len * -1 - Incomplete request * -2 - parse failed */ struct http_head *http_head_init(int buffsize, HTTP_VERSION version) { struct http_head *http_head = NULL; unsigned char *buffer = NULL; http_head = zalloc(1, sizeof(*http_head)); if (http_head == NULL) { goto errout; } INIT_LIST_HEAD(&http_head->field_head.list); hash_init(http_head->field_map); INIT_LIST_HEAD(&http_head->params.list); hash_init(http_head->params_map); buffer = zalloc(1, buffsize); if (buffer == NULL) { goto errout; } http_head->buff = buffer; http_head->buff_size = buffsize; http_head->http_version = version; return http_head; errout: if (buffer) { free(buffer); } if (http_head) { free(http_head); } return NULL; } struct http_head_fields *http_head_first_fields(struct http_head *http_head) { struct http_head_fields *first = NULL; first = list_first_entry(&http_head->field_head.list, struct http_head_fields, list); if (first->name == NULL && first->value == NULL) { return NULL; } return first; } const char *http_head_get_fields_value(struct http_head *http_head, const char *name) { uint32_t key; struct http_head_fields *filed = NULL; key = hash_string_case(name); hash_for_each_possible(http_head->field_map, filed, node, key) { if (strncasecmp(filed->name, name, 128) == 0) { return filed->value; } } return NULL; } struct http_head_fields *http_head_next_fields(struct http_head_fields *fields) { struct http_head_fields *next = NULL; next = list_next_entry(fields, list); if (next->name == NULL && next->value == NULL) { return NULL; } return next; } const char *http_head_fields_get_name(struct http_head_fields *fields) { if (fields == NULL) { return NULL; } return fields->name; } const char *http_head_fields_get_value(struct http_head_fields *fields) { if (fields == NULL) { return NULL; } return fields->value; } int http_head_lookup_fields(struct http_head_fields *fields, const char **name, const char **value) { if (fields == NULL) { return -1; } if (name) { *name = fields->name; } if (value) { *value = fields->value; } return 0; } const char *http_head_get_params_value(struct http_head *http_head, const char *name) { uint32_t key; struct http_params *params = NULL; key = hash_string_case(name); hash_for_each_possible(http_head->params_map, params, node, key) { if (strncasecmp(params->name, name, 128) == 0) { return params->value; } } return NULL; } HTTP_METHOD http_head_get_method(struct http_head *http_head) { return http_head->method; } const char *http_head_get_url(struct http_head *http_head) { return http_head->url; } const char *http_head_get_httpversion(struct http_head *http_head) { return http_head->version; } int http_head_get_httpcode(struct http_head *http_head) { return http_head->code; } const char *http_head_get_httpcode_msg(struct http_head *http_head) { return http_head->code_msg; } HTTP_HEAD_TYPE http_head_get_head_type(struct http_head *http_head) { return http_head->head_type; } const unsigned char *http_head_get_data(struct http_head *http_head) { return http_head->data; } int http_head_get_data_len(struct http_head *http_head) { return http_head->data_len; } int _http_head_buffer_left_len(struct http_head *http_head) { return http_head->buff_size - http_head->buff_len; } uint8_t *_http_head_buffer_get_end(struct http_head *http_head) { return http_head->buff + http_head->buff_len; } uint8_t *_http_head_buffer_append(struct http_head *http_head, const uint8_t *data, int data_len) { if (http_head == NULL || data_len < 0) { return NULL; } if (http_head->buff_len + data_len > http_head->buff_size) { return NULL; } if (data != NULL) { memcpy(http_head->buff + http_head->buff_len, data, data_len); } http_head->buff_len += data_len; return (http_head->buff + http_head->buff_len); } int _http_head_add_param(struct http_head *http_head, const char *name, const char *value) { uint32_t key = 0; struct http_params *params = NULL; params = zalloc(1, sizeof(*params)); if (params == NULL) { return -1; } params->name = name; params->value = value; list_add_tail(¶ms->list, &http_head->params.list); key = hash_string_case(name); hash_add(http_head->params_map, ¶ms->node, key); return 0; } int http_head_add_param(struct http_head *http_head, const char *name, const char *value) { if (http_head == NULL || name == NULL || value == NULL) { return -1; } return _http_head_add_param(http_head, name, value); } int http_head_set_url(struct http_head *http_head, const char *url) { if (http_head == NULL || url == NULL) { return -1; } http_head->url = url; return 0; } int http_head_set_httpversion(struct http_head *http_head, const char *version) { if (http_head == NULL || version == NULL) { return -1; } http_head->version = version; return 0; } int http_head_set_httpcode(struct http_head *http_head, int code, const char *msg) { if (http_head == NULL || code < 0 || msg == NULL) { return -1; } http_head->code = code; http_head->code_msg = msg; return 0; } int http_head_set_head_type(struct http_head *http_head, HTTP_HEAD_TYPE head_type) { if (http_head == NULL || head_type == HTTP_HEAD_INVALID) { return -1; } http_head->head_type = head_type; return 0; } int http_head_set_method(struct http_head *http_head, HTTP_METHOD method) { if (http_head == NULL || method == HTTP_METHOD_INVALID) { return -1; } http_head->method = method; return 0; } int http_head_set_data(struct http_head *http_head, const void *data, int len) { if (http_head == NULL || data == NULL || len < 0) { return -1; } http_head->data = (unsigned char *)data; http_head->data_len = len; return 0; } static int _http_head_add_fields(struct http_head *http_head, const char *name, const char *value) { uint32_t key = 0; struct http_head_fields *fields = NULL; fields = zalloc(1, sizeof(*fields)); if (fields == NULL) { return -1; } fields->name = name; fields->value = value; list_add_tail(&fields->list, &http_head->field_head.list); key = hash_string_case(name); hash_add(http_head->field_map, &fields->node, key); return 0; } int http_head_add_fields(struct http_head *http_head, const char *name, const char *value) { if (http_head == NULL || name == NULL || value == NULL) { return -1; } return _http_head_add_fields(http_head, name, value); } int _http_head_parse_params(struct http_head *http_head, char *url, int url_len) { char *tmp_ptr = NULL; char *field_start = NULL; char *param_start = NULL; char *field = NULL; char *value = NULL; if (url == NULL) { return -1; } param_start = strstr(url, "?"); if (param_start == NULL) { return 0; } *param_start = '\0'; param_start++; for (tmp_ptr = param_start; tmp_ptr < url + url_len; tmp_ptr++) { if (field_start == NULL) { field_start = tmp_ptr; } if (field == NULL) { if (*tmp_ptr == '=') { *tmp_ptr = '\0'; field = field_start; field_start = NULL; } continue; } if (value == NULL) { if (*tmp_ptr == '&' || tmp_ptr == url + url_len - 1) { *tmp_ptr = '\0'; value = field_start; field_start = NULL; if (_http_head_add_param(http_head, field, value) != 0) { return -2; } field = NULL; value = NULL; } continue; } } return 0; } const char *http_method_str(HTTP_METHOD method) { switch (method) { case HTTP_METHOD_GET: return "GET"; case HTTP_METHOD_POST: return "POST"; case HTTP_METHOD_PUT: return "PUT"; case HTTP_METHOD_DELETE: return "DELETE"; case HTTP_METHOD_TRACE: return "TRACE"; case HTTP_METHOD_CONNECT: return "CONNECT"; default: return "INVALID"; } } HTTP_METHOD _http_method_parse(const char *method) { if (method == NULL) { return HTTP_METHOD_INVALID; } if (strncmp(method, "GET", sizeof("GET")) == 0) { return HTTP_METHOD_GET; } else if (strncmp(method, "POST", sizeof("POST")) == 0) { return HTTP_METHOD_POST; } else if (strncmp(method, "PUT", sizeof("PUT")) == 0) { return HTTP_METHOD_PUT; } else if (strncmp(method, "DELETE", sizeof("DELETE")) == 0) { return HTTP_METHOD_DELETE; } else if (strncmp(method, "TRACE", sizeof("TRACE")) == 0) { return HTTP_METHOD_TRACE; } else if (strncmp(method, "CONNECT", sizeof("CONNECT")) == 0) { return HTTP_METHOD_CONNECT; } return HTTP_METHOD_INVALID; } int http_head_parse(struct http_head *http_head, const unsigned char *data, int data_len) { if (http_head->http_version == HTTP_VERSION_1_1) { return http_head_parse_http1_1(http_head, data, data_len); } else if (http_head->http_version == HTTP_VERSION_3_0) { return http_head_parse_http3_0(http_head, data, data_len); } return -2; } int http_head_serialize(struct http_head *http_head, void *buffer, int buffer_len) { if (http_head == NULL || buffer == NULL || buffer_len <= 0) { return -1; } if (http_head->http_version == HTTP_VERSION_1_1) { return http_head_serialize_http1_1(http_head, buffer, buffer_len); } else if (http_head->http_version == HTTP_VERSION_3_0) { return http_head_serialize_http3_0(http_head, buffer, buffer_len); } return -2; } void http_head_destroy(struct http_head *http_head) { struct http_head_fields *fields = NULL, *tmp; struct http_params *params = NULL, *tmp_params; list_for_each_entry_safe(fields, tmp, &http_head->field_head.list, list) { list_del(&fields->list); free(fields); } list_for_each_entry_safe(params, tmp_params, &http_head->params.list, list) { list_del(¶ms->list); free(params); } if (http_head->buff) { free(http_head->buff); } free(http_head); } ================================================ FILE: src/http_parse/http_parse.h ================================================ /************************************************************************* * * 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 . */ #ifndef _HTTP_PARSE_HTTP_H_ #define _HTTP_PARSE_HTTP_H_ #include "smartdns/lib/hash.h" #include "smartdns/lib/hashtable.h" #include "smartdns/lib/jhash.h" #include "smartdns/lib/list.h" #include "smartdns/http_parse.h" #ifdef __cplusplus extern "C" { #endif /*__cplusplus */ struct http_head_fields { struct hlist_node node; struct list_head list; const char *name; const char *value; }; struct http_params { struct hlist_node node; struct list_head list; const char *name; const char *value; }; struct http_head { HTTP_VERSION http_version; HTTP_HEAD_TYPE head_type; HTTP_METHOD method; const char *url; const char *version; int code; const char *code_msg; int buff_size; int buff_len; uint8_t *buff; int head_ok; int head_len; const uint8_t *data; int data_len; int expect_data_len; struct http_head_fields field_head; struct http_params params; DECLARE_HASHTABLE(field_map, 4); DECLARE_HASHTABLE(params_map, 4); }; int _http_head_buffer_left_len(struct http_head *http_head); uint8_t *_http_head_buffer_get_end(struct http_head *http_head); uint8_t *_http_head_buffer_append(struct http_head *http_head, const uint8_t *data, int data_len); int _http_head_add_param(struct http_head *http_head, const char *name, const char *value); int _http_head_parse_params(struct http_head *http_head, char *url, int url_len); HTTP_METHOD _http_method_parse(const char *method); #ifdef __cplusplus } #endif /*__cplusplus */ #endif ================================================ FILE: src/http_parse/qpack.c ================================================ /************************************************************************* * * 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 . */ #include "qpack.h" static const uint8_t _qpack_huffman_bit[64] = { 249, 50, 115, 39, 38, 79, 147, 39, 38, 100, 249, 50, 114, 100, 242, 100, 228, 228, 206, 77, 52, 228, 204, 203, 202, 114, 102, 79, 147, 39, 76, 156, 153, 62, 76, 156, 156, 153, 62, 76, 156, 153, 60, 154, 100, 242, 102, 79, 147, 39, 38, 83, 228, 201, 201, 147, 211, 39, 38, 79, 38, 78, 76, 178}; static const uint8_t _qpack_huffman_val[512] = { /* 0: | X: 44 */ 44, /* 1: |0 X: 17 */ 16, /* 2: |00 X: 10 */ 8, /* 3: |000 X: 7 */ 4, /* 4: |0000 X: 6 */ 2, /* 5: |00000 V: 48 */ 48, /* 6: |00001 V: 49 */ 49, /* 7: |0001 X: 9 */ 2, /* 8: |00010 V: 50 */ 50, /* 9: |00011 V: 97 */ 97, /* 10: |001 X: 14 */ 4, /* 11: |0010 X: 13 */ 2, /* 12: |00100 V: 99 */ 99, /* 13: |00101 V: 101 */ 101, /* 14: |0011 X: 16 */ 2, /* 15: |00110 V: 105 */ 105, /* 16: |00111 V: 111 */ 111, /* 17: |01 X: 29 */ 12, /* 18: |010 X: 22 */ 4, /* 19: |0100 X: 21 */ 2, /* 20: |01000 V: 115 */ 115, /* 21: |01001 V: 116 */ 116, /* 22: |0101 X: 26 */ 4, /* 23: |01010 X: 25 */ 2, /* 24: |010100 V: 32 */ 32, /* 25: |010101 V: 37 */ 37, /* 26: |01011 X: 28 */ 2, /* 27: |010110 V: 45 */ 45, /* 28: |010111 V: 46 */ 46, /* 29: |011 X: 37 */ 8, /* 30: |0110 X: 34 */ 4, /* 31: |01100 X: 33 */ 2, /* 32: |011000 V: 47 */ 47, /* 33: |011001 V: 51 */ 51, /* 34: |01101 X: 36 */ 2, /* 35: |011010 V: 52 */ 52, /* 36: |011011 V: 53 */ 53, /* 37: |0111 X: 41 */ 4, /* 38: |01110 X: 40 */ 2, /* 39: |011100 V: 54 */ 54, /* 40: |011101 V: 55 */ 55, /* 41: |01111 X: 43 */ 2, /* 42: |011110 V: 56 */ 56, /* 43: |011111 V: 57 */ 57, /* 44: |1 X: 80 */ 36, /* 45: |10 X: 61 */ 16, /* 46: |100 X: 54 */ 8, /* 47: |1000 X: 51 */ 4, /* 48: |10000 X: 50 */ 2, /* 49: |100000 V: 61 */ 61, /* 50: |100001 V: 65 */ 65, /* 51: |10001 X: 53 */ 2, /* 52: |100010 V: 95 */ 95, /* 53: |100011 V: 98 */ 98, /* 54: |1001 X: 58 */ 4, /* 55: |10010 X: 57 */ 2, /* 56: |100100 V: 100 */ 100, /* 57: |100101 V: 102 */ 102, /* 58: |10011 X: 60 */ 2, /* 59: |100110 V: 103 */ 103, /* 60: |100111 V: 104 */ 104, /* 61: |101 X: 69 */ 8, /* 62: |1010 X: 66 */ 4, /* 63: |10100 X: 65 */ 2, /* 64: |101000 V: 108 */ 108, /* 65: |101001 V: 109 */ 109, /* 66: |10101 X: 68 */ 2, /* 67: |101010 V: 110 */ 110, /* 68: |101011 V: 112 */ 112, /* 69: |1011 X: 73 */ 4, /* 70: |10110 X: 72 */ 2, /* 71: |101100 V: 114 */ 114, /* 72: |101101 V: 117 */ 117, /* 73: |10111 X: 77 */ 4, /* 74: |101110 X: 76 */ 2, /* 75: |1011100 V: 58 */ 58, /* 76: |1011101 V: 66 */ 66, /* 77: |101111 X: 79 */ 2, /* 78: |1011110 V: 67 */ 67, /* 79: |1011111 V: 68 */ 68, /* 80: |11 X: 112 */ 32, /* 81: |110 X: 97 */ 16, /* 82: |1100 X: 90 */ 8, /* 83: |11000 X: 87 */ 4, /* 84: |110000 X: 86 */ 2, /* 85: |1100000 V: 69 */ 69, /* 86: |1100001 V: 70 */ 70, /* 87: |110001 X: 89 */ 2, /* 88: |1100010 V: 71 */ 71, /* 89: |1100011 V: 72 */ 72, /* 90: |11001 X: 94 */ 4, /* 91: |110010 X: 93 */ 2, /* 92: |1100100 V: 73 */ 73, /* 93: |1100101 V: 74 */ 74, /* 94: |110011 X: 96 */ 2, /* 95: |1100110 V: 75 */ 75, /* 96: |1100111 V: 76 */ 76, /* 97: |1101 X: 105 */ 8, /* 98: |11010 X: 102 */ 4, /* 99: |110100 X: 101 */ 2, /* 100: |1101000 V: 77 */ 77, /* 101: |1101001 V: 78 */ 78, /* 102: |110101 X: 104 */ 2, /* 103: |1101010 V: 79 */ 79, /* 104: |1101011 V: 80 */ 80, /* 105: |11011 X: 109 */ 4, /* 106: |110110 X: 108 */ 2, /* 107: |1101100 V: 81 */ 81, /* 108: |1101101 V: 82 */ 82, /* 109: |110111 X: 111 */ 2, /* 110: |1101110 V: 83 */ 83, /* 111: |1101111 V: 84 */ 84, /* 112: |111 X: 128 */ 16, /* 113: |1110 X: 121 */ 8, /* 114: |11100 X: 118 */ 4, /* 115: |111000 X: 117 */ 2, /* 116: |1110000 V: 85 */ 85, /* 117: |1110001 V: 86 */ 86, /* 118: |111001 X: 120 */ 2, /* 119: |1110010 V: 87 */ 87, /* 120: |1110011 V: 89 */ 89, /* 121: |11101 X: 125 */ 4, /* 122: |111010 X: 124 */ 2, /* 123: |1110100 V: 106 */ 106, /* 124: |1110101 V: 107 */ 107, /* 125: |111011 X: 127 */ 2, /* 126: |1110110 V: 113 */ 113, /* 127: |1110111 V: 118 */ 118, /* 128: |1111 X: 136 */ 8, /* 129: |11110 X: 133 */ 4, /* 130: |111100 X: 132 */ 2, /* 131: |1111000 V: 119 */ 119, /* 132: |1111001 V: 120 */ 120, /* 133: |111101 X: 135 */ 2, /* 134: |1111010 V: 121 */ 121, /* 135: |1111011 V: 122 */ 122, /* 136: |11111 X: 144 */ 8, /* 137: |111110 X: 141 */ 4, /* 138: |1111100 X: 140 */ 2, /* 139: |11111000 V: 38 */ 38, /* 140: |11111001 V: 42 */ 42, /* 141: |1111101 X: 143 */ 2, /* 142: |11111010 V: 44 */ 44, /* 143: |11111011 V: 59 */ 59, /* 144: |111111 X: 148 */ 4, /* 145: |1111110 X: 147 */ 2, /* 146: |11111100 V: 88 */ 88, /* 147: |11111101 V: 90 */ 90, /* 148: |1111111 X: 156 */ 8, /* 149: |11111110 X: 153 */ 4, /* 150: |11111110|0 X: 152 */ 2, /* 151: |11111110|00 V: 33 */ 33, /* 152: |11111110|01 V: 34 */ 34, /* 153: |11111110|1 X: 155 */ 2, /* 154: |11111110|10 V: 40 */ 40, /* 155: |11111110|11 V: 41 */ 41, /* 156: |11111111| X: 162 */ 6, /* 157: |11111111|0 X: 159 */ 2, /* 158: |11111111|00 V: 63 */ 63, /* 159: |11111111|01 X: 161 */ 2, /* 160: |11111111|010 V: 39 */ 39, /* 161: |11111111|011 V: 43 */ 43, /* 162: |11111111|1 X: 168 */ 6, /* 163: |11111111|10 X: 165 */ 2, /* 164: |11111111|100 V: 124 */ 124, /* 165: |11111111|101 X: 167 */ 2, /* 166: |11111111|1010 V: 35 */ 35, /* 167: |11111111|1011 V: 62 */ 62, /* 168: |11111111|11 X: 176 */ 8, /* 169: |11111111|110 X: 173 */ 4, /* 170: |11111111|1100 X: 172 */ 2, /* 171: |11111111|11000 V: 0 */ 0, /* 172: |11111111|11001 V: 36 */ 36, /* 173: |11111111|1101 X: 175 */ 2, /* 174: |11111111|11010 V: 64 */ 64, /* 175: |11111111|11011 V: 91 */ 91, /* 176: |11111111|111 X: 180 */ 4, /* 177: |11111111|1110 X: 179 */ 2, /* 178: |11111111|11100 V: 93 */ 93, /* 179: |11111111|11101 V: 126 */ 126, /* 180: |11111111|1111 X: 184 */ 4, /* 181: |11111111|11110 X: 183 */ 2, /* 182: |11111111|111100 V: 94 */ 94, /* 183: |11111111|111101 V: 125 */ 125, /* 184: |11111111|11111 X: 188 */ 4, /* 185: |11111111|111110 X: 187 */ 2, /* 186: |11111111|1111100 V: 60 */ 60, /* 187: |11111111|1111101 V: 96 */ 96, /* 188: |11111111|111111 X: 190 */ 2, /* 189: |11111111|1111110 V: 123 */ 123, /* 190: |11111111|1111111 X: 220 */ 30, /* 191: |11111111|11111110| X: 201 */ 10, /* 192: |11111111|11111110|0 X: 196 */ 4, /* 193: |11111111|11111110|00 X: 195 */ 2, /* 194: |11111111|11111110|000 V: 92 */ 92, /* 195: |11111111|11111110|001 V: 195 */ 195, /* 196: |11111111|11111110|01 X: 198 */ 2, /* 197: |11111111|11111110|010 V: 208 */ 208, /* 198: |11111111|11111110|011 X: 200 */ 2, /* 199: |11111111|11111110|0110 V: 128 */ 128, /* 200: |11111111|11111110|0111 V: 130 */ 130, /* 201: |11111111|11111110|1 X: 209 */ 8, /* 202: |11111111|11111110|10 X: 206 */ 4, /* 203: |11111111|11111110|100 X: 205 */ 2, /* 204: |11111111|11111110|1000 V: 131 */ 131, /* 205: |11111111|11111110|1001 V: 162 */ 162, /* 206: |11111111|11111110|101 X: 208 */ 2, /* 207: |11111111|11111110|1010 V: 184 */ 184, /* 208: |11111111|11111110|1011 V: 194 */ 194, /* 209: |11111111|11111110|11 X: 213 */ 4, /* 210: |11111111|11111110|110 X: 212 */ 2, /* 211: |11111111|11111110|1100 V: 224 */ 224, /* 212: |11111111|11111110|1101 V: 226 */ 226, /* 213: |11111111|11111110|111 X: 217 */ 4, /* 214: |11111111|11111110|1110 X: 216 */ 2, /* 215: |11111111|11111110|11100 V: 153 */ 153, /* 216: |11111111|11111110|11101 V: 161 */ 161, /* 217: |11111111|11111110|1111 X: 219 */ 2, /* 218: |11111111|11111110|11110 V: 167 */ 167, /* 219: |11111111|11111110|11111 V: 172 */ 172, /* 220: |11111111|11111111| X: 266 */ 46, /* 221: |11111111|11111111|0 X: 237 */ 16, /* 222: |11111111|11111111|00 X: 230 */ 8, /* 223: |11111111|11111111|000 X: 227 */ 4, /* 224: |11111111|11111111|0000 X: 226 */ 2, /* 225: |11111111|11111111|00000 V: 176 */ 176, /* 226: |11111111|11111111|00001 V: 177 */ 177, /* 227: |11111111|11111111|0001 X: 229 */ 2, /* 228: |11111111|11111111|00010 V: 179 */ 179, /* 229: |11111111|11111111|00011 V: 209 */ 209, /* 230: |11111111|11111111|001 X: 234 */ 4, /* 231: |11111111|11111111|0010 X: 233 */ 2, /* 232: |11111111|11111111|00100 V: 216 */ 216, /* 233: |11111111|11111111|00101 V: 217 */ 217, /* 234: |11111111|11111111|0011 X: 236 */ 2, /* 235: |11111111|11111111|00110 V: 227 */ 227, /* 236: |11111111|11111111|00111 V: 229 */ 229, /* 237: |11111111|11111111|01 X: 251 */ 14, /* 238: |11111111|11111111|010 X: 244 */ 6, /* 239: |11111111|11111111|0100 X: 241 */ 2, /* 240: |11111111|11111111|01000 V: 230 */ 230, /* 241: |11111111|11111111|01001 X: 243 */ 2, /* 242: |11111111|11111111|010010 V: 129 */ 129, /* 243: |11111111|11111111|010011 V: 132 */ 132, /* 244: |11111111|11111111|0101 X: 248 */ 4, /* 245: |11111111|11111111|01010 X: 247 */ 2, /* 246: |11111111|11111111|010100 V: 133 */ 133, /* 247: |11111111|11111111|010101 V: 134 */ 134, /* 248: |11111111|11111111|01011 X: 250 */ 2, /* 249: |11111111|11111111|010110 V: 136 */ 136, /* 250: |11111111|11111111|010111 V: 146 */ 146, /* 251: |11111111|11111111|011 X: 259 */ 8, /* 252: |11111111|11111111|0110 X: 256 */ 4, /* 253: |11111111|11111111|01100 X: 255 */ 2, /* 254: |11111111|11111111|011000 V: 154 */ 154, /* 255: |11111111|11111111|011001 V: 156 */ 156, /* 256: |11111111|11111111|01101 X: 257 */ 2, /* 257: |11111111|11111111|011010 V: 160 */ 160, /* 258: |11111111|11111111|011011 V: 163 */ 163, /* 259: |11111111|11111111|0111 X: 263 */ 4, /* 260: |11111111|11111111|01110 X: 262 */ 2, /* 261: |11111111|11111111|011100 V: 164 */ 164, /* 262: |11111111|11111111|011101 V: 169 */ 169, /* 263: |11111111|11111111|01111 X: 265 */ 2, /* 264: |11111111|11111111|011110 V: 170 */ 170, /* 265: |11111111|11111111|011111 V: 173 */ 173, /* 266: |11111111|11111111|1 X: 306 */ 40, /* 267: |11111111|11111111|10 X: 283 */ 16, /* 268: |11111111|11111111|100 X: 276 */ 8, /* 269: |11111111|11111111|1000 X: 273 */ 4, /* 270: |11111111|11111111|10000 X: 272 */ 2, /* 271: |11111111|11111111|100000 V: 178 */ 178, /* 272: |11111111|11111111|100001 V: 181 */ 181, /* 273: |11111111|11111111|10001 X: 275 */ 2, /* 274: |11111111|11111111|100010 V: 185 */ 185, /* 275: |11111111|11111111|100011 V: 186 */ 186, /* 276: |11111111|11111111|1001 X: 280 */ 4, /* 277: |11111111|11111111|10010 X: 279 */ 2, /* 278: |11111111|11111111|100100 V: 187 */ 187, /* 279: |11111111|11111111|100101 V: 189 */ 189, /* 280: |11111111|11111111|10011 X: 282 */ 2, /* 281: |11111111|11111111|100110 V: 190 */ 190, /* 282: |11111111|11111111|100111 V: 196 */ 196, /* 283: |11111111|11111111|101 X: 291 */ 8, /* 284: |11111111|11111111|1010 X: 288 */ 4, /* 285: |11111111|11111111|10100 X: 287 */ 2, /* 286: |11111111|11111111|101000 V: 198 */ 198, /* 287: |11111111|11111111|101001 V: 228 */ 228, /* 288: |11111111|11111111|10101 X: 290 */ 2, /* 289: |11111111|11111111|101010 V: 232 */ 232, /* 290: |11111111|11111111|101011 V: 233 */ 233, /* 291: |11111111|11111111|1011 X: 299 */ 8, /* 292: |11111111|11111111|10110 X: 296 */ 4, /* 293: |11111111|11111111|101100 X: 295 */ 2, /* 294: |11111111|11111111|1011000 V: 1 */ 1, /* 295: |11111111|11111111|1011001 V: 135 */ 135, /* 296: |11111111|11111111|101101 X: 298 */ 2, /* 297: |11111111|11111111|1011010 V: 137 */ 137, /* 298: |11111111|11111111|1011011 V: 138 */ 138, /* 299: |11111111|11111111|10111 X: 303 */ 4, /* 300: |11111111|11111111|101110 X: 302 */ 2, /* 301: |11111111|11111111|1011100 V: 139 */ 139, /* 302: |11111111|11111111|1011101 V: 140 */ 140, /* 303: |11111111|11111111|101111 X: 305 */ 2, /* 304: |11111111|11111111|1011110 V: 141 */ 141, /* 305: |11111111|11111111|1011111 V: 143 */ 143, /* 306: |11111111|11111111|11 X: 338 */ 32, /* 307: |11111111|11111111|110 X: 323 */ 16, /* 308: |11111111|11111111|1100 X: 316 */ 8, /* 309: |11111111|11111111|11000 X: 313 */ 4, /* 310: |11111111|11111111|110000 X: 312 */ 2, /* 311: |11111111|11111111|1100000 V: 147 */ 147, /* 312: |11111111|11111111|1100001 V: 149 */ 149, /* 313: |11111111|11111111|110001 X: 315 */ 2, /* 314: |11111111|11111111|1100010 V: 150 */ 150, /* 315: |11111111|11111111|1100011 V: 151 */ 151, /* 316: |11111111|11111111|11001 X: 320 */ 4, /* 317: |11111111|11111111|110010 X: 319 */ 2, /* 318: |11111111|11111111|1100100 V: 152 */ 152, /* 319: |11111111|11111111|1100101 V: 155 */ 155, /* 320: |11111111|11111111|110011 X: 322 */ 2, /* 321: |11111111|11111111|1100110 V: 157 */ 157, /* 322: |11111111|11111111|1100111 V: 158 */ 158, /* 323: |11111111|11111111|1101 X: 331 */ 8, /* 324: |11111111|11111111|11010 X: 328 */ 4, /* 325: |11111111|11111111|110100 X: 327 */ 2, /* 326: |11111111|11111111|1101000 V: 165 */ 165, /* 327: |11111111|11111111|1101001 V: 166 */ 166, /* 328: |11111111|11111111|110101 X: 330 */ 2, /* 329: |11111111|11111111|1101010 V: 168 */ 168, /* 330: |11111111|11111111|1101011 V: 174 */ 174, /* 331: |11111111|11111111|11011 X: 335 */ 4, /* 332: |11111111|11111111|110110 X: 334 */ 2, /* 333: |11111111|11111111|1101100 V: 175 */ 175, /* 334: |11111111|11111111|1101101 V: 180 */ 180, /* 335: |11111111|11111111|110111 X: 337 */ 2, /* 336: |11111111|11111111|1101110 V: 182 */ 182, /* 337: |11111111|11111111|1101111 V: 183 */ 183, /* 338: |11111111|11111111|111 X: 360 */ 22, /* 339: |11111111|11111111|1110 X: 347 */ 8, /* 340: |11111111|11111111|11100 X: 344 */ 4, /* 341: |11111111|11111111|111000 X: 343 */ 2, /* 342: |11111111|11111111|1110000 V: 188 */ 188, /* 343: |11111111|11111111|1110001 V: 191 */ 191, /* 344: |11111111|11111111|111001 X: 346 */ 2, /* 345: |11111111|11111111|1110010 V: 197 */ 197, /* 346: |11111111|11111111|1110011 V: 231 */ 231, /* 347: |11111111|11111111|11101 X: 353 */ 6, /* 348: |11111111|11111111|111010 X: 350 */ 2, /* 349: |11111111|11111111|1110100 V: 239 */ 239, /* 350: |11111111|11111111|1110101 X: 352 */ 2, /* 351: |11111111|11111111|11101010 V: 9 */ 9, /* 352: |11111111|11111111|11101011 V: 142 */ 142, /* 353: |11111111|11111111|111011 X: 357 */ 4, /* 354: |11111111|11111111|1110110 X: 356 */ 2, /* 355: |11111111|11111111|11101100 V: 144 */ 144, /* 356: |11111111|11111111|11101101 V: 145 */ 145, /* 357: |11111111|11111111|1110111 X: 359 */ 2, /* 358: |11111111|11111111|11101110 V: 148 */ 148, /* 359: |11111111|11111111|11101111 V: 159 */ 159, /* 360: |11111111|11111111|1111 X: 380 */ 20, /* 361: |11111111|11111111|11110 X: 369 */ 8, /* 362: |11111111|11111111|111100 X: 366 */ 4, /* 363: |11111111|11111111|1111000 X: 365 */ 2, /* 364: |11111111|11111111|11110000 V: 171 */ 171, /* 365: |11111111|11111111|11110001 V: 206 */ 206, /* 366: |11111111|11111111|1111001 X: 368 */ 2, /* 367: |11111111|11111111|11110010 V: 215 */ 215, /* 368: |11111111|11111111|11110011 V: 225 */ 225, /* 369: |11111111|11111111|111101 X: 373 */ 4, /* 370: |11111111|11111111|1111010 X: 372 */ 2, /* 371: |11111111|11111111|11110100 V: 236 */ 236, /* 372: |11111111|11111111|11110101 V: 237 */ 237, /* 373: |11111111|11111111|1111011 X: 377 */ 4, /* 374: |11111111|11111111|11110110| X: 376 */ 2, /* 375: |11111111|11111111|11110110|0 V: 199 */ 199, /* 376: |11111111|11111111|11110110|1 V: 207 */ 207, /* 377: |11111111|11111111|11110111| X: 379 */ 2, /* 378: |11111111|11111111|11110111|0 V: 234 */ 234, /* 379: |11111111|11111111|11110111|1 V: 235 */ 235, /* 380: |11111111|11111111|11111 X: 414 */ 34, /* 381: |11111111|11111111|111110 X: 397 */ 16, /* 382: |11111111|11111111|1111100 X: 390 */ 8, /* 383: |11111111|11111111|11111000| X: 387 */ 4, /* 384: |11111111|11111111|11111000|0 X: 386 */ 2, /* 385: |11111111|11111111|11111000|00 V: 192 */ 192, /* 386: |11111111|11111111|11111000|01 V: 193 */ 193, /* 387: |11111111|11111111|11111000|1 X: 389 */ 2, /* 388: |11111111|11111111|11111000|10 V: 200 */ 200, /* 389: |11111111|11111111|11111000|11 V: 201 */ 201, /* 390: |11111111|11111111|11111001| X: 394 */ 4, /* 391: |11111111|11111111|11111001|0 X: 393 */ 2, /* 392: |11111111|11111111|11111001|00 V: 202 */ 202, /* 393: |11111111|11111111|11111001|01 V: 205 */ 205, /* 394: |11111111|11111111|11111001|1 X: 396 */ 2, /* 395: |11111111|11111111|11111001|10 V: 210 */ 210, /* 396: |11111111|11111111|11111001|11 V: 213 */ 213, /* 397: |11111111|11111111|1111101 X: 405 */ 8, /* 398: |11111111|11111111|11111010| X: 402 */ 4, /* 399: |11111111|11111111|11111010|0 X: 401 */ 2, /* 400: |11111111|11111111|11111010|00 V: 218 */ 218, /* 401: |11111111|11111111|11111010|01 V: 219 */ 219, /* 402: |11111111|11111111|11111010|1 X: 404 */ 2, /* 403: |11111111|11111111|11111010|10 V: 238 */ 238, /* 404: |11111111|11111111|11111010|11 V: 240 */ 240, /* 405: |11111111|11111111|11111011| X: 409 */ 4, /* 406: |11111111|11111111|11111011|0 X: 408 */ 2, /* 407: |11111111|11111111|11111011|00 V: 242 */ 242, /* 408: |11111111|11111111|11111011|01 V: 243 */ 243, /* 409: |11111111|11111111|11111011|1 X: 411 */ 2, /* 410: |11111111|11111111|11111011|10 V: 255 */ 255, /* 411: |11111111|11111111|11111011|11 X: 413 */ 2, /* 412: |11111111|11111111|11111011|110 V: 203 */ 203, /* 413: |11111111|11111111|11111011|111 V: 204 */ 204, /* 414: |11111111|11111111|111111 X: 446 */ 32, /* 415: |11111111|11111111|1111110 X: 431 */ 16, /* 416: |11111111|11111111|11111100| X: 424 */ 8, /* 417: |11111111|11111111|11111100|0 X: 421 */ 4, /* 418: |11111111|11111111|11111100|00 X: 420 */ 2, /* 419: |11111111|11111111|11111100|000 V: 211 */ 211, /* 420: |11111111|11111111|11111100|001 V: 212 */ 212, /* 421: |11111111|11111111|11111100|01 X: 423 */ 2, /* 422: |11111111|11111111|11111100|010 V: 214 */ 214, /* 423: |11111111|11111111|11111100|011 V: 221 */ 221, /* 424: |11111111|11111111|11111100|1 X: 428 */ 4, /* 425: |11111111|11111111|11111100|10 X: 427 */ 2, /* 426: |11111111|11111111|11111100|100 V: 222 */ 222, /* 427: |11111111|11111111|11111100|101 V: 223 */ 223, /* 428: |11111111|11111111|11111100|11 X: 430 */ 2, /* 429: |11111111|11111111|11111100|110 V: 241 */ 241, /* 430: |11111111|11111111|11111100|111 V: 244 */ 244, /* 431: |11111111|11111111|11111101| X: 439 */ 8, /* 432: |11111111|11111111|11111101|0 X: 436 */ 4, /* 433: |11111111|11111111|11111101|00 X: 435 */ 2, /* 434: |11111111|11111111|11111101|000 V: 245 */ 245, /* 435: |11111111|11111111|11111101|001 V: 246 */ 246, /* 436: |11111111|11111111|11111101|01 X: 438 */ 2, /* 437: |11111111|11111111|11111101|010 V: 247 */ 247, /* 438: |11111111|11111111|11111101|011 V: 248 */ 248, /* 439: |11111111|11111111|11111101|1 X: 443 */ 4, /* 440: |11111111|11111111|11111101|10 X: 442 */ 2, /* 441: |11111111|11111111|11111101|100 V: 250 */ 250, /* 442: |11111111|11111111|11111101|101 V: 251 */ 251, /* 443: |11111111|11111111|11111101|11 X: 445 */ 2, /* 444: |11111111|11111111|11111101|110 V: 252 */ 252, /* 445: |11111111|11111111|11111101|111 V: 253 */ 253, /* 446: |11111111|11111111|1111111 X: 476 */ 30, /* 447: |11111111|11111111|11111110| X: 461 */ 14, /* 448: |11111111|11111111|11111110|0 X: 454 */ 6, /* 449: |11111111|11111111|11111110|00 X: 451 */ 2, /* 450: |11111111|11111111|11111110|000 V: 254 */ 254, /* 451: |11111111|11111111|11111110|001 X: 453 */ 2, /* 452: |11111111|11111111|11111110|0010 V: 2 */ 2, /* 453: |11111111|11111111|11111110|0011 V: 3 */ 3, /* 454: |11111111|11111111|11111110|01 X: 458 */ 4, /* 455: |11111111|11111111|11111110|010 X: 457 */ 2, /* 456: |11111111|11111111|11111110|0100 V: 4 */ 4, /* 457: |11111111|11111111|11111110|0101 V: 5 */ 5, /* 458: |11111111|11111111|11111110|011 X: 460 */ 2, /* 459: |11111111|11111111|11111110|0110 V: 6 */ 6, /* 460: |11111111|11111111|11111110|0111 V: 7 */ 7, /* 461: |11111111|11111111|11111110|1 X: 469 */ 8, /* 462: |11111111|11111111|11111110|10 X: 466 */ 4, /* 463: |11111111|11111111|11111110|100 X: 465 */ 2, /* 464: |11111111|11111111|11111110|1000 V: 8 */ 8, /* 465: |11111111|11111111|11111110|1001 V: 11 */ 11, /* 466: |11111111|11111111|11111110|101 X: 468 */ 2, /* 467: |11111111|11111111|11111110|1010 V: 12 */ 12, /* 468: |11111111|11111111|11111110|1011 V: 14 */ 14, /* 469: |11111111|11111111|11111110|11 X: 473 */ 4, /* 470: |11111111|11111111|11111110|110 X: 472 */ 2, /* 471: |11111111|11111111|11111110|1100 V: 15 */ 15, /* 472: |11111111|11111111|11111110|1101 V: 16 */ 16, /* 473: |11111111|11111111|11111110|111 X: 475 */ 2, /* 474: |11111111|11111111|11111110|1110 V: 17 */ 17, /* 475: |11111111|11111111|11111110|1111 V: 18 */ 18, /* 476: |11111111|11111111|11111111| X: 492 */ 16, /* 477: |11111111|11111111|11111111|0 X: 485 */ 8, /* 478: |11111111|11111111|11111111|00 X: 482 */ 4, /* 479: |11111111|11111111|11111111|000 X: 481 */ 2, /* 480: |11111111|11111111|11111111|0000 V: 19 */ 19, /* 481: |11111111|11111111|11111111|0001 V: 20 */ 20, /* 482: |11111111|11111111|11111111|001 X: 484 */ 2, /* 483: |11111111|11111111|11111111|0010 V: 21 */ 21, /* 484: |11111111|11111111|11111111|0011 V: 23 */ 23, /* 485: |11111111|11111111|11111111|01 X: 489 */ 4, /* 486: |11111111|11111111|11111111|010 X: 488 */ 2, /* 487: |11111111|11111111|11111111|0100 V: 24 */ 24, /* 488: |11111111|11111111|11111111|0101 V: 25 */ 25, /* 489: |11111111|11111111|11111111|011 X: 491 */ 2, /* 490: |11111111|11111111|11111111|0110 V: 26 */ 26, /* 491: |11111111|11111111|11111111|0111 V: 27 */ 27, /* 492: |11111111|11111111|11111111|1 X: 500 */ 8, /* 493: |11111111|11111111|11111111|10 X: 497 */ 4, /* 494: |11111111|11111111|11111111|100 X: 496 */ 2, /* 495: |11111111|11111111|11111111|1000 V: 28 */ 28, /* 496: |11111111|11111111|11111111|1001 V: 29 */ 29, /* 497: |11111111|11111111|11111111|101 X: 499 */ 2, /* 498: |11111111|11111111|11111111|1010 V: 30 */ 30, /* 499: |11111111|11111111|11111111|1011 V: 31 */ 31, /* 500: |11111111|11111111|11111111|11 X: 504 */ 4, /* 501: |11111111|11111111|11111111|110 X: 503 */ 2, /* 502: |11111111|11111111|11111111|1100 V: 127 */ 127, /* 503: |11111111|11111111|11111111|1101 V: 220 */ 220, /* 504: |11111111|11111111|11111111|111 X: 506 */ 2, /* 505: |11111111|11111111|11111111|1110 V: 249 */ 249, /* 506: |11111111|11111111|11111111|1111 X: 510 */ 4, /* 507: |11111111|11111111|11111111|11110 X: 509 */ 2, /* 508: |11111111|11111111|11111111|111100 V: 10 */ 10, /* 509: |11111111|11111111|11111111|111101 V: 13 */ 13, /* 510: |11111111|11111111|11111111|11111 X: 512 */ 2, /* 511: |11111111|11111111|11111111|111110 V: 22 */ 22}; int qpack_huffman_decode(const uint8_t *bytes, const uint8_t *bytes_max, uint8_t *decoded, size_t max_decoded, size_t *nb_decoded) { int ret = 0; uint64_t val_in = 0; int bits_in = 0; size_t decoded_index = 0; int index = 0; int was_all_ones = 1; while (1) { int bit; int index_64 = index >> 3; int b_index = 7 - (index & 7); while (bits_in < 57 && bytes < bytes_max) { uint64_t added = *bytes++; int shift = 64 - bits_in - 8; added <<= shift; val_in |= added; bits_in += 8; } if ((_qpack_huffman_bit[index_64] >> b_index) & 1) { if (bits_in <= 0) { break; } bit = (val_in >> 63) & 1; val_in <<= 1; bits_in--; if (bit) { index += _qpack_huffman_val[index]; } else { index++; was_all_ones = 0; } if (index >= 512) { break; } } else if (decoded_index < max_decoded) { decoded[decoded_index++] = _qpack_huffman_val[index]; index = 0; was_all_ones = 1; } else { was_all_ones = 1; break; } } if (!was_all_ones) { ret = -1; } *nb_decoded = decoded_index; return ret; } /* clang-format off */ static struct qpack_header_field _http3_static_header_table[] = { {.name = ":authority", .value = ""}, {.name = ":path", .value = "/"}, {.name = "age", .value = "0"}, {.name = "content-disposition", .value = ""}, {.name = "content-length", .value = "0"}, {.name = "cookie", .value = ""}, {.name = "date", .value = ""}, {.name = "etag", .value = ""}, {.name = "if-modified-since", .value = ""}, {.name = "if-none-match", .value = ""}, {.name = "last-modified", .value = ""}, {.name = "link", .value = ""}, {.name = "location", .value = ""}, {.name = "referer", .value = ""}, {.name = "set-cookie", .value = ""}, {.name = ":method", .value = "CONNECT"}, {.name = ":method", .value = "DELETE"}, {.name = ":method", .value = "GET"}, {.name = ":method", .value = "HEAD"}, {.name = ":method", .value = "OPTIONS"}, {.name = ":method", .value = "POST"}, {.name = ":method", .value = "PUT"}, {.name = ":scheme", .value = "http"}, {.name = ":scheme", .value = "https"}, {.name = ":status", .value = "103"}, {.name = ":status", .value = "200"}, {.name = ":status", .value = "304"}, {.name = ":status", .value = "404"}, {.name = ":status", .value = "503"}, {.name = "accept", .value = "*/*"}, {.name = "accept", .value = "application/dns-message"}, {.name = "accept-encoding", .value = "gzip, .value = deflate, .value = br"}, {.name = "accept-ranges", .value = "bytes"}, {.name = "access-control-allow-headers", .value = "cache-control"}, {.name = "access-control-allow-headers", .value = "content-type"}, {.name = "access-control-allow-origin", .value = "*"}, {.name = "cache-control", .value = "max-age=0"}, {.name = "cache-control", .value = "max-age=2592000"}, {.name = "cache-control", .value = "max-age=604800"}, {.name = "cache-control", .value = "no-cache"}, {.name = "cache-control", .value = "no-store"}, {.name = "cache-control", .value = "public, .value = max-age=31536000"}, {.name = "content-encoding", .value = "br"}, {.name = "content-encoding", .value = "gzip"}, {.name = "content-type", .value = "application/dns-message"}, {.name = "content-type", .value = "application/javascript"}, {.name = "content-type", .value = "application/json"}, {.name = "content-type", .value = "application/x-www-form-urlencoded"}, {.name = "content-type", .value = "image/gif"}, {.name = "content-type", .value = "image/jpeg"}, {.name = "content-type", .value = "image/png"}, {.name = "content-type", .value = "text/css"}, {.name = "content-type", .value = "text/html; charset=utf-8"}, {.name = "content-type", .value = "text/plain"}, {.name = "content-type", .value = "text/plain;charset=utf-8"}, {.name = "range", .value = "bytes=0-"}, {.name = "strict-transport-security", .value = "max-age=31536000"}, {.name = "strict-transport-security", .value = "max-age=31536000; includesubdomains"}, {.name = "strict-transport-security", .value = "max-age=31536000; includesubdomains; preload"}, {.name = "vary", .value = "accept-encoding"}, {.name = "vary", .value = "origin"}, {.name = "x-content-type-options", .value = "nosniff"}, {.name = "x-xss-protection", .value = "1; mode=block"}, {.name = ":status", .value = "100"}, {.name = ":status", .value = "204"}, {.name = ":status", .value = "206"}, {.name = ":status", .value = "302"}, {.name = ":status", .value = "400"}, {.name = ":status", .value = "403"}, {.name = ":status", .value = "421"}, {.name = ":status", .value = "425"}, {.name = ":status", .value = "500"}, {.name = "accept-language", .value = ""}, {.name = "access-control-allow-credentials", .value = "FALSE"}, {.name = "access-control-allow-credentials", .value = "TRUE"}, {.name = "access-control-allow-headers", .value = "*"}, {.name = "access-control-allow-methods", .value = "get"}, {.name = "access-control-allow-methods", .value = "get, .value = post, .value = options"}, {.name = "access-control-allow-methods", .value = "options"}, {.name = "access-control-expose-headers", .value = "content-length"}, {.name = "access-control-request-headers", .value = "content-type"}, {.name = "access-control-request-method", .value = "get"}, {.name = "access-control-request-method", .value = "post"}, {.name = "alt-svc", .value = "clear"}, {.name = "authorization"}, {.name = "content-security-policy", .value = "script-src 'none'; object-src 'none'; base-uri 'none'"}, {.name = "early-data", .value = "1"}, {.name = "expect-ct", .value = ""}, {.name = "forwarded", .value = ""}, {.name = "if-range", .value = ""}, {.name = "origin", .value = ""}, {.name = "purpose", .value = "prefetch"}, {.name = "server", .value = ""}, {.name = "timing-allow-origin", .value = "*"}, {.name = "upgrade-insecure-requests", .value = "1"}, {.name = "user-agent", .value = ""}, {.name = "x-forwarded-for", .value = ""}, {.name = "x-frame-options", .value = "deny"}, {.name = "x-frame-options", .value = "sameorigin"}, }; static int _http3_static_header_table_len = sizeof(_http3_static_header_table) / sizeof(struct qpack_header_field); /* clang-format on */ struct qpack_header_field *qpack_get_static_header_field(int index) { if (index < 0 || index >= _http3_static_header_table_len) { return NULL; } return &_http3_static_header_table[index]; } ================================================ FILE: src/http_parse/qpack.h ================================================ /************************************************************************* * * 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 . */ #ifndef _QPACK_H #define _QPACK_H #include #include #ifdef __cplusplus extern "C" { #endif struct qpack_header_field { const char *name; const char *value; }; struct qpack_header_field *qpack_get_static_header_field(int index); int qpack_huffman_decode(const uint8_t *bytes, const uint8_t *bytes_max, uint8_t *decoded, size_t max_decoded, size_t *nb_decoded); #ifdef __cplusplus } #endif #endif // !_QPACK_H ================================================ FILE: src/lib/art.c ================================================ /* Copyright (c) 2012, Armon Dadgar All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARMON DADGAR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "smartdns/lib/art.h" #include "smartdns/util.h" // #ifdef __i386__ // #include // #else #ifdef __amd64__ #include #endif // #endif /** * Macros to manipulate pointer tags */ #define IS_LEAF(x) (((uintptr_t)x & 1)) #define SET_LEAF(x) ((void*)((uintptr_t)x | 1)) #define LEAF_RAW(x) ((art_leaf*)((void*)((uintptr_t)x & ~1))) /** * Forward declarations */ static void add_child32(art_node32 *n, art_node **ref, unsigned char c, void *child); static void add_child48(art_node48 *n, art_node **ref, unsigned char c, void *child); /** * Allocates a node of the given type, * initializes to zero and sets the type. */ static art_node* alloc_node(uint8_t type) { art_node* n; void *mem = NULL; switch (type) { case NODE4: mem = (art_node*)calloc(1, sizeof(art_node4)); break; case NODE16: mem = (art_node*)calloc(1, sizeof(art_node16)); break; case NODE32: mem = (art_node*)calloc(1, sizeof(art_node32)); break; case NODE48: mem = (art_node*)calloc(1, sizeof(art_node48)); break; case NODE256: mem = (art_node*)calloc(1, sizeof(art_node256)); break; default: BUG("alloc_node: invalid type"); } if (mem == NULL) { BUG("alloc_node: calloc failed"); } n = mem; n->type = type; return n; } /** * Initializes an ART tree * @return 0 on success. */ int art_tree_init(art_tree *t) { t->root = NULL; t->size = 0; return 0; } // Recursively destroys the tree static void destroy_node(art_node *n) { // Break if null if (!n) return; // Special case leafs if (IS_LEAF(n)) { free(LEAF_RAW(n)); return; } // Handle each node type int i, idx; union { art_node4 *p1; art_node16 *p2; art_node32 *p3; art_node48 *p4; art_node256 *p5; } p; switch (n->type) { case NODE4: p.p1 = (art_node4*)n; for (i=0;inum_children;i++) { destroy_node(p.p1->children[i]); } break; case NODE16: p.p2 = (art_node16*)n; for (i=0;inum_children;i++) { destroy_node(p.p2->children[i]); } break; case NODE32: p.p3 = (art_node32*)n; for (i=0;inum_children;i++) { destroy_node(p.p3->children[i]); } break; case NODE48: p.p4 = (art_node48*)n; for (i=0;i<256;i++) { idx = ((art_node48*)n)->keys[i]; if (!idx) continue; destroy_node(p.p4->children[idx-1]); } break; case NODE256: p.p5 = (art_node256*)n; for (i=0;i<256;i++) { if (p.p5->children[i]) destroy_node(p.p5->children[i]); } break; default: BUG("destroy_node: invalid type"); } // Free ourself on the way up free(n); } /** * Destroys an ART tree * @return 0 on success. */ int art_tree_destroy(art_tree *t) { destroy_node(t->root); return 0; } /** * Returns the size of the ART tree. */ #ifndef BROKEN_GCC_C99_INLINE extern inline uint64_t art_size(art_tree *t); #endif static art_node** find_child(art_node *n, unsigned char c) { int i, mask, bitfield; union { art_node4 *p1; art_node16 *p2; art_node32 *p3; art_node48 *p4; art_node256 *p5; } p; switch (n->type) { case NODE4: p.p1 = (art_node4*)n; for (i=0 ; i < n->num_children; i++) { /* this cast works around a bug in gcc 5.1 when unrolling loops * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 */ if (((unsigned char*)p.p1->keys)[i] == c) return &p.p1->children[i]; } break; { case NODE16: p.p2 = (art_node16*)n; // support non-86 architectures // #ifdef __i386__ // // Compare the key to all 16 stored keys // __m128i cmp; // cmp = _mm_cmpeq_epi8(_mm_set1_epi8(c), // _mm_loadu_si128((__m128i*)p.p2->keys)); // // Use a mask to ignore children that don't exist // mask = (1 << n->num_children) - 1; // bitfield = _mm_movemask_epi8(cmp) & mask; // #else #ifdef __amd64__ // Compare the key to all 16 stored keys __m128i cmp; cmp = _mm_cmpeq_epi8(_mm_set1_epi8(c), _mm_loadu_si128((__m128i*)p.p2->keys)); // Use a mask to ignore children that don't exist mask = (1 << n->num_children) - 1; bitfield = _mm_movemask_epi8(cmp) & mask; #else // Compare the key to all 16 stored keys bitfield = 0; for (i = 0; i < 16; ++i) { if (p.p2->keys[i] == c) bitfield |= (1 << i); } // Use a mask to ignore children that don't exist mask = (1 << n->num_children) - 1; bitfield &= mask; #endif // #endif /* * If we have a match (any bit set) then we can * return the pointer match using ctz to get * the index. */ if (bitfield) return &p.p2->children[__builtin_ctz(bitfield)]; break; } case NODE32: p.p3 = (art_node32*)n; // Linear search for NODE32 for (i = 0; i < n->num_children; i++) { if (p.p3->keys[i] == c) return &p.p3->children[i]; } break; case NODE48: p.p4 = (art_node48*)n; i = p.p4->keys[c]; if (i) return &p.p4->children[i-1]; break; case NODE256: p.p5 = (art_node256*)n; if (p.p5->children[c]) return &p.p5->children[c]; break; default: BUG("find_child: invalid type"); } return NULL; } // Simple inlined if static inline int min(int a, int b) { return (a < b) ? a : b; } /** * Returns the number of prefix characters shared between * the key and node. */ static int check_prefix(const art_node *n, const unsigned char *key, int key_len, int depth) { int max_cmp = min(min(n->partial_len, MAX_PREFIX_LEN), key_len - depth); int idx; for (idx=0; idx < max_cmp; idx++) { if (n->partial[idx] != key[depth+idx]) return idx; } return idx; } /** * Checks if a leaf matches * @return 0 on success. */ static int leaf_matches(const art_leaf *n, const unsigned char *key, int key_len, int depth) { (void)depth; // Fail if the key lengths are different if (n->key_len != (uint32_t)key_len) return 1; // Compare the keys starting at the depth return memcmp(n->key, key, key_len); } /** * Searches for a value in the ART tree * @arg t The tree * @arg key The key * @arg key_len The length of the key * @return NULL if the item was not found, otherwise * the value pointer is returned. */ void* art_search(const art_tree *t, const unsigned char *key, int key_len) { art_node **child; art_node *n = t->root; int prefix_len, depth = 0; while (n) { // Might be a leaf if (IS_LEAF(n)) { n = (art_node*)LEAF_RAW(n); // Check if the expanded path matches if (!leaf_matches((art_leaf*)n, key, key_len, depth)) { return ((art_leaf*)n)->value; } return NULL; } // Bail if the prefix does not match if (n->partial_len) { prefix_len = check_prefix(n, key, key_len, depth); if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len)) return NULL; depth = depth + n->partial_len; } // Recursively search child = find_child(n, key[depth]); n = (child) ? *child : NULL; depth++; } return NULL; } // Find the minimum leaf under a node static art_leaf* minimum(const art_node *n) { // Handle base cases if (!n) return NULL; if (IS_LEAF(n)) return LEAF_RAW(n); int idx; switch (n->type) { case NODE4: return minimum(((const art_node4*)n)->children[0]); case NODE16: return minimum(((const art_node16*)n)->children[0]); case NODE32: return minimum(((const art_node32*)n)->children[0]); case NODE48: idx=0; while (!((const art_node48*)n)->keys[idx]) idx++; idx = ((const art_node48*)n)->keys[idx] - 1; return minimum(((const art_node48*)n)->children[idx]); case NODE256: idx=0; while (!((const art_node256*)n)->children[idx]) idx++; return minimum(((const art_node256*)n)->children[idx]); default: BUG("minimum: invalid type"); return NULL; } } // Find the maximum leaf under a node static art_leaf* maximum(const art_node *n) { // Handle base cases if (!n) return NULL; if (IS_LEAF(n)) return LEAF_RAW(n); int idx; switch (n->type) { case NODE4: return maximum(((const art_node4*)n)->children[n->num_children-1]); case NODE16: return maximum(((const art_node16*)n)->children[n->num_children-1]); case NODE32: return maximum(((const art_node32*)n)->children[n->num_children-1]); case NODE48: idx=255; while (!((const art_node48*)n)->keys[idx]) idx--; idx = ((const art_node48*)n)->keys[idx] - 1; return maximum(((const art_node48*)n)->children[idx]); case NODE256: idx=255; while (!((const art_node256*)n)->children[idx]) idx--; return maximum(((const art_node256*)n)->children[idx]); default: BUG("maximum: invalid type"); return NULL; } } /** * Returns the minimum valued leaf */ art_leaf* art_minimum(art_tree *t) { return minimum((art_node*)t->root); } /** * Returns the maximum valued leaf */ art_leaf* art_maximum(art_tree *t) { return maximum((art_node*)t->root); } static art_leaf* make_leaf(const unsigned char *key, int key_len, void *value) { art_leaf *l = (art_leaf*)calloc(1, sizeof(art_leaf)+key_len+1); if (l == NULL) { return NULL; } l->value = value; l->key_len = key_len; memcpy(l->key, key, key_len); return l; } static int longest_common_prefix(art_leaf *l1, art_leaf *l2, int depth) { int max_cmp = min(l1->key_len, l2->key_len) - depth; int idx; for (idx=0; idx < max_cmp; idx++) { if (l1->key[depth+idx] != l2->key[depth+idx]) return idx; } return idx; } static void copy_header(art_node *dest, art_node *src) { dest->num_children = src->num_children; dest->partial_len = src->partial_len; memcpy(dest->partial, src->partial, min(MAX_PREFIX_LEN, src->partial_len)); } static void add_child256(art_node256 *n, art_node **ref, unsigned char c, void *child) { (void)ref; n->n.num_children++; n->children[c] = (art_node*)child; } static void add_child48(art_node48 *n, art_node **ref, unsigned char c, void *child) { if (n->n.num_children < 48) { int pos = 0; while (n->children[pos]) pos++; n->children[pos] = (art_node*)child; n->keys[c] = pos + 1; n->n.num_children++; } else { art_node256 *new_node = (art_node256*)alloc_node(NODE256); int i; for (i=0;i<256;i++) { if (n->keys[i]) { new_node->children[i] = n->children[n->keys[i] - 1]; } } copy_header((art_node*)new_node, (art_node*)n); *ref = (art_node*)new_node; free(n); add_child256(new_node, ref, c, child); } } static void add_child32(art_node32 *n, art_node **ref, unsigned char c, void *child) { if (n->n.num_children < 32) { unsigned idx; // Find insertion point for (idx = 0; idx < n->n.num_children; idx++) { if (c < n->keys[idx]) break; } // Shift to make room memmove(n->keys+idx+1, n->keys+idx, n->n.num_children - idx); memmove(n->children+idx+1, n->children+idx, (n->n.num_children - idx)*sizeof(void*)); // Insert element n->keys[idx] = c; n->children[idx] = (art_node*)child; n->n.num_children++; } else { art_node48 *new_node = (art_node48*)alloc_node(NODE48); int i; // Copy the child pointers and populate the key map memcpy(new_node->children, n->children, sizeof(void*)*n->n.num_children); for (i=0;in.num_children;i++) { new_node->keys[n->keys[i]] = i + 1; } copy_header((art_node*)new_node, (art_node*)n); *ref = (art_node*)new_node; free(n); add_child48(new_node, ref, c, child); } } static void add_child16(art_node16 *n, art_node **ref, unsigned char c, void *child) { if (n->n.num_children < 16) { unsigned mask = (1 << n->n.num_children) - 1; // support non-x86 architectures // #ifdef __i386__ // __m128i cmp; // // Compare the key to all 16 stored keys // cmp = _mm_cmplt_epi8(_mm_set1_epi8(c), // _mm_loadu_si128((__m128i*)n->keys)); // // Use a mask to ignore children that don't exist // unsigned bitfield = _mm_movemask_epi8(cmp) & mask; // #else #ifdef __amd64__ __m128i cmp; // Compare the key to all 16 stored keys cmp = _mm_cmplt_epi8(_mm_set1_epi8(c), _mm_loadu_si128((__m128i*)n->keys)); // Use a mask to ignore children that don't exist unsigned bitfield = _mm_movemask_epi8(cmp) & mask; #else // Compare the key to all 16 stored keys unsigned bitfield = 0; int i; for (i = 0; i < 16; ++i) { if (c < n->keys[i]) bitfield |= (1 << i); } // Use a mask to ignore children that don't exist bitfield &= mask; #endif // #endif // Check if less than any unsigned idx; if (bitfield) { idx = __builtin_ctz(bitfield); memmove(n->keys+idx+1,n->keys+idx,n->n.num_children-idx); memmove(n->children+idx+1,n->children+idx, (n->n.num_children-idx)*sizeof(void*)); } else idx = n->n.num_children; // Set the child n->keys[idx] = c; n->children[idx] = (art_node*)child; n->n.num_children++; } else { art_node32 *new_node = (art_node32*)alloc_node(NODE32); // Copy the child pointers and keys memcpy(new_node->children, n->children, sizeof(void*)*n->n.num_children); memcpy(new_node->keys, n->keys, sizeof(unsigned char)*n->n.num_children); copy_header((art_node*)new_node, (art_node*)n); *ref = (art_node*)new_node; free(n); add_child32(new_node, ref, c, child); } } static void add_child4(art_node4 *n, art_node **ref, unsigned char c, void *child) { if (n->n.num_children < 4) { int idx; for (idx=0; idx < n->n.num_children; idx++) { if (c < n->keys[idx]) break; } // Shift to make room memmove(n->keys+idx+1, n->keys+idx, n->n.num_children - idx); memmove(n->children+idx+1, n->children+idx, (n->n.num_children - idx)*sizeof(void*)); // Insert element n->keys[idx] = c; n->children[idx] = (art_node*)child; n->n.num_children++; } else { art_node16 *new_node = (art_node16*)alloc_node(NODE16); // Copy the child pointers and the key map memcpy(new_node->children, n->children, sizeof(void*)*n->n.num_children); memcpy(new_node->keys, n->keys, sizeof(unsigned char)*n->n.num_children); copy_header((art_node*)new_node, (art_node*)n); *ref = (art_node*)new_node; free(n); add_child16(new_node, ref, c, child); } } static void add_child(art_node *n, art_node **ref, unsigned char c, void *child) { switch (n->type) { case NODE4: return add_child4((art_node4*)n, ref, c, child); case NODE16: return add_child16((art_node16*)n, ref, c, child); case NODE32: return add_child32((art_node32*)n, ref, c, child); case NODE48: return add_child48((art_node48*)n, ref, c, child); case NODE256: return add_child256((art_node256*)n, ref, c, child); default: BUG("add_child: invalid type"); } } /** * Calculates the index at which the prefixes mismatch */ static int prefix_mismatch(const art_node *n, const unsigned char *key, int key_len, int depth) { int max_cmp = min(min(MAX_PREFIX_LEN, n->partial_len), key_len - depth); int idx; for (idx=0; idx < max_cmp; idx++) { if (n->partial[idx] != key[depth+idx]) return idx; } // If the prefix is short we can avoid finding a leaf if (n->partial_len > MAX_PREFIX_LEN) { // Prefix is longer than what we've checked, find a leaf art_leaf *l = minimum(n); max_cmp = min(l->key_len, key_len)- depth; for (; idx < max_cmp; idx++) { if (l->key[idx+depth] != key[depth+idx]) return idx; } } return idx; } static void* recursive_insert(art_node *n, art_node **ref, const unsigned char *key, int key_len, void *value, int depth, int *old) { // If we are at a NULL node, inject a leaf if (!n) { *ref = (art_node*)SET_LEAF(make_leaf(key, key_len, value)); return NULL; } // If we are at a leaf, we need to replace it with a node if (IS_LEAF(n)) { art_leaf *l = LEAF_RAW(n); // Check if we are updating an existing value if (!leaf_matches(l, key, key_len, depth)) { *old = 1; void *old_val = l->value; l->value = value; return old_val; } // New value, we must split the leaf into a node4 art_node4 *new_node = (art_node4*)alloc_node(NODE4); // Create a new leaf art_leaf *l2 = make_leaf(key, key_len, value); // Determine longest prefix int longest_prefix = longest_common_prefix(l, l2, depth); new_node->n.partial_len = longest_prefix; memcpy(new_node->n.partial, key+depth, min(MAX_PREFIX_LEN, longest_prefix)); // Add the leafs to the new node4 *ref = (art_node*)new_node; add_child4(new_node, ref, l->key[depth+longest_prefix], SET_LEAF(l)); add_child4(new_node, ref, l2->key[depth+longest_prefix], SET_LEAF(l2)); return NULL; } // Check if given node has a prefix if (n->partial_len) { // Determine if the prefixes differ, since we need to split int prefix_diff = prefix_mismatch(n, key, key_len, depth); if ((uint32_t)prefix_diff >= n->partial_len) { depth += n->partial_len; goto RECURSE_SEARCH; } // Create a new node art_node4 *new_node = (art_node4*)alloc_node(NODE4); *ref = (art_node*)new_node; new_node->n.partial_len = prefix_diff; memcpy(new_node->n.partial, n->partial, min(MAX_PREFIX_LEN, prefix_diff)); // Adjust the prefix of the old node if (n->partial_len <= MAX_PREFIX_LEN) { add_child4(new_node, ref, n->partial[prefix_diff], n); n->partial_len -= (prefix_diff+1); memmove(n->partial, n->partial+prefix_diff+1, min(MAX_PREFIX_LEN, n->partial_len)); } else { n->partial_len -= (prefix_diff+1); art_leaf *l = minimum(n); add_child4(new_node, ref, l->key[depth+prefix_diff], n); memcpy(n->partial, l->key+depth+prefix_diff+1, min(MAX_PREFIX_LEN, n->partial_len)); } // Insert the new leaf art_leaf *l = make_leaf(key, key_len, value); add_child4(new_node, ref, key[depth+prefix_diff], SET_LEAF(l)); return NULL; } RECURSE_SEARCH:; // Find a child to recurse to art_node **child = find_child(n, key[depth]); if (child) { return recursive_insert(*child, child, key, key_len, value, depth+1, old); } // No child, node goes within us art_leaf *l = make_leaf(key, key_len, value); add_child(n, ref, key[depth], SET_LEAF(l)); return NULL; } /** * Inserts a new value into the ART tree * @arg t The tree * @arg key The key * @arg key_len The length of the key * @arg value Opaque value. * @return NULL if the item was newly inserted, otherwise * the old value pointer is returned. */ void* art_insert(art_tree *t, const unsigned char *key, int key_len, void *value) { int old_val = 0; void *old = recursive_insert(t->root, &t->root, key, key_len, value, 0, &old_val); if (!old_val) t->size++; return old; } static void remove_child256(art_node256 *n, art_node **ref, unsigned char c) { n->children[c] = NULL; n->n.num_children--; // Resize to a node48 on underflow, not immediately to prevent // trashing if we sit on the 48/49 boundary if (n->n.num_children == 37) { art_node48 *new_node = (art_node48*)alloc_node(NODE48); *ref = (art_node*)new_node; copy_header((art_node*)new_node, (art_node*)n); int pos = 0; int i; for (i=0;i<256;i++) { if (n->children[i]) { new_node->children[pos] = n->children[i]; new_node->keys[i] = pos + 1; pos++; } } free(n); } } static void remove_child48(art_node48 *n, art_node **ref, unsigned char c) { int pos = n->keys[c]; n->keys[c] = 0; n->children[pos-1] = NULL; n->n.num_children--; if (n->n.num_children == 31) { art_node32 *new_node = (art_node32*)alloc_node(NODE32); *ref = (art_node*)new_node; copy_header((art_node*)new_node, (art_node*)n); int child = 0; int i; for (i=0;i<256;i++) { pos = n->keys[i]; if (pos) { new_node->keys[child] = i; new_node->children[child] = n->children[pos - 1]; child++; } } free(n); } } static void remove_child32(art_node32 *n, art_node **ref, art_node **l) { int pos = l - n->children; memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos); memmove(n->children+pos, n->children+pos+1, (n->n.num_children - 1 - pos)*sizeof(void*)); n->n.num_children--; if (n->n.num_children == 15) { art_node16 *new_node = (art_node16*)alloc_node(NODE16); *ref = (art_node*)new_node; copy_header((art_node*)new_node, (art_node*)n); memcpy(new_node->keys, n->keys, 16); memcpy(new_node->children, n->children, 16*sizeof(void*)); free(n); } } static void remove_child16(art_node16 *n, art_node **ref, art_node **l) { int pos = l - n->children; memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos); memmove(n->children+pos, n->children+pos+1, (n->n.num_children - 1 - pos)*sizeof(void*)); n->n.num_children--; if (n->n.num_children == 3) { art_node4 *new_node = (art_node4*)alloc_node(NODE4); *ref = (art_node*)new_node; copy_header((art_node*)new_node, (art_node*)n); memcpy(new_node->keys, n->keys, 4); memcpy(new_node->children, n->children, 4*sizeof(void*)); free(n); } } static void remove_child4(art_node4 *n, art_node **ref, art_node **l) { int pos = l - n->children; memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos); memmove(n->children+pos, n->children+pos+1, (n->n.num_children - 1 - pos)*sizeof(void*)); n->n.num_children--; // Remove nodes with only a single child if (n->n.num_children == 1) { art_node *child = n->children[0]; if (!IS_LEAF(child)) { // Concatenate the prefixes int prefix = n->n.partial_len; if (prefix < MAX_PREFIX_LEN) { n->n.partial[prefix] = n->keys[0]; prefix++; } if (prefix < MAX_PREFIX_LEN) { int sub_prefix = min(child->partial_len, MAX_PREFIX_LEN - prefix); memcpy(n->n.partial+prefix, child->partial, sub_prefix); prefix += sub_prefix; } // Store the prefix in the child memcpy(child->partial, n->n.partial, min(prefix, MAX_PREFIX_LEN)); child->partial_len += n->n.partial_len + 1; } *ref = child; free(n); } } static void remove_child(art_node *n, art_node **ref, unsigned char c, art_node **l) { switch (n->type) { case NODE4: return remove_child4((art_node4*)n, ref, l); case NODE16: return remove_child16((art_node16*)n, ref, l); case NODE32: return remove_child32((art_node32*)n, ref, l); case NODE48: return remove_child48((art_node48*)n, ref, c); case NODE256: return remove_child256((art_node256*)n, ref, c); default: BUG("remove_child: invalid type"); } } static art_leaf* recursive_delete(art_node *n, art_node **ref, const unsigned char *key, int key_len, int depth) { // Search terminated if (!n) return NULL; // Handle hitting a leaf node if (IS_LEAF(n)) { art_leaf *l = LEAF_RAW(n); if (!leaf_matches(l, key, key_len, depth)) { *ref = NULL; return l; } return NULL; } // Bail if the prefix does not match if (n->partial_len) { int prefix_len = check_prefix(n, key, key_len, depth); if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len)) { return NULL; } depth = depth + n->partial_len; } // Find child node art_node **child = find_child(n, key[depth]); if (!child) return NULL; // If the child is leaf, delete from this node if (IS_LEAF(*child)) { art_leaf *l = LEAF_RAW(*child); if (!leaf_matches(l, key, key_len, depth)) { remove_child(n, ref, key[depth], child); return l; } return NULL; // Recurse } else { return recursive_delete(*child, child, key, key_len, depth+1); } } /** * Deletes a value from the ART tree * @arg t The tree * @arg key The key * @arg key_len The length of the key * @return NULL if the item was not found, otherwise * the value pointer is returned. */ void* art_delete(art_tree *t, const unsigned char *key, int key_len) { art_leaf *l = recursive_delete(t->root, &t->root, key, key_len, 0); if (l) { t->size--; void *old = l->value; free(l); return old; } return NULL; } // Recursively iterates over the tree static int recursive_iter(art_node *n, art_callback cb, void *data) { // Handle base cases if (!n) return 0; if (IS_LEAF(n)) { art_leaf *l = LEAF_RAW(n); return cb(data, (const unsigned char*)l->key, l->key_len, l->value); } int idx, res; int i; switch (n->type) { case NODE4: for (i=0; i < n->num_children; i++) { res = recursive_iter(((art_node4*)n)->children[i], cb, data); if (res) return res; } break; case NODE16: for (i=0; i < n->num_children; i++) { res = recursive_iter(((art_node16*)n)->children[i], cb, data); if (res) return res; } break; case NODE32: for (i=0; i < n->num_children; i++) { res = recursive_iter(((art_node32*)n)->children[i], cb, data); if (res) return res; } break; case NODE48: for (i=0; i < 256; i++) { idx = ((art_node48*)n)->keys[i]; if (!idx) continue; res = recursive_iter(((art_node48*)n)->children[idx-1], cb, data); if (res) return res; } break; case NODE256: for (i=0; i < 256; i++) { if (!((art_node256*)n)->children[i]) continue; res = recursive_iter(((art_node256*)n)->children[i], cb, data); if (res) return res; } break; default: BUG("recursive_iter: invalid type"); } return 0; } /** * Iterates through the entries pairs in the map, * invoking a callback for each. The call back gets a * key, value for each and returns an integer stop value. * If the callback returns non-zero, then the iteration stops. * @arg t The tree to iterate over * @arg cb The callback function to invoke * @arg data Opaque handle passed to the callback * @return 0 on success, or the return of the callback. */ int art_iter(art_tree *t, art_callback cb, void *data) { return recursive_iter(t->root, cb, data); } /** * Checks if a leaf prefix matches * @return 0 on success. */ static int leaf_prefix_matches(const art_leaf *n, const unsigned char *prefix, int prefix_len) { // Fail if the key length is too short if (n->key_len < (uint32_t)prefix_len) return 1; // Compare the keys return memcmp(n->key, prefix, prefix_len); } /** * Iterates through the entries pairs in the map, * invoking a callback for each that matches a given prefix. * The call back gets a key, value for each and returns an integer stop value. * If the callback returns non-zero, then the iteration stops. * @arg t The tree to iterate over * @arg prefix The prefix of keys to read * @arg prefix_len The length of the prefix * @arg cb The callback function to invoke * @arg data Opaque handle passed to the callback * @return 0 on success, or the return of the callback. */ int art_iter_prefix(art_tree *t, const unsigned char *key, int key_len, art_callback cb, void *data) { art_node **child; art_node *n = t->root; int prefix_len, depth = 0; while (n) { // Might be a leaf if (IS_LEAF(n)) { n = (art_node*)LEAF_RAW(n); // Check if the expanded path matches if (!leaf_prefix_matches((art_leaf*)n, key, key_len)) { art_leaf *l = (art_leaf*)n; return cb(data, (const unsigned char*)l->key, l->key_len, l->value); } return 0; } // If the depth matches the prefix, we need to handle this node if (depth == key_len) { art_leaf *l = minimum(n); if (!leaf_prefix_matches(l, key, key_len)) return recursive_iter(n, cb, data); return 0; } // Bail if the prefix does not match if (n->partial_len) { prefix_len = prefix_mismatch(n, key, key_len, depth); // Guard if the mis-match is longer than the MAX_PREFIX_LEN if ((uint32_t)prefix_len > n->partial_len) { prefix_len = n->partial_len; } // If there is no match, search is terminated if (!prefix_len) { return 0; // If we've matched the prefix, iterate on this node } else if (depth + prefix_len == key_len) { return recursive_iter(n, cb, data); } // if there is a full match, go deeper depth = depth + n->partial_len; } // Recursively search child = find_child(n, key[depth]); n = (child) ? *child : NULL; depth++; } return 0; } static int str_prefix_matches(const art_leaf *n, const unsigned char *str, int str_len) { // Fail if the key length is too short if (n->key_len > (uint32_t)str_len) return 1; // Compare the keys return memcmp(str, n->key, n->key_len); } static void art_copy_key(art_leaf *leaf, unsigned char *key, int *key_len) { int len; if (key == NULL || key_len == NULL) { return; } len = (int)leaf->key_len > *key_len ? *key_len : (int)leaf->key_len; memcpy(key, leaf->key, len); *key_len = len; } void *art_substring(const art_tree *t, const unsigned char *str, int str_len, unsigned char *key, int *key_len) { art_node **child; art_node *n = t->root; art_node *m; art_leaf *found = NULL; int prefix_len, depth = 0; while (n) { // Might be a leaf if (IS_LEAF(n)) { n = (art_node*)LEAF_RAW(n); // Check if the expanded path matches if (!str_prefix_matches((art_leaf*)n, str, str_len)) { found = (art_leaf*)n; } break; } // Check if current is leaf child = find_child(n, 0); m = (child) ? *child : NULL; if (m && IS_LEAF(m)) { m = (art_node*)LEAF_RAW(m); // Check if the expanded path matches if (!str_prefix_matches((art_leaf*)m, str, str_len)) { found = (art_leaf*)m; } } // Bail if the prefix does not match if (n->partial_len) { prefix_len = check_prefix(n, str, str_len, depth); if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len)) break; depth = depth + n->partial_len; } // Recursively search child = find_child(n, str[depth]); n = (child) ? *child : NULL; depth++; } if (found == NULL) { return NULL; } art_copy_key(found, key, key_len); return found->value; } void art_substring_walk(const art_tree *t, const unsigned char *str, int str_len, walk_func func, void *arg) { art_node **child; art_node *n = t->root; art_node *m; art_leaf *found = NULL; int prefix_len, depth = 0; int stop_search = 0; while (n && stop_search == 0) { // Might be a leaf if (IS_LEAF(n)) { n = (art_node*)LEAF_RAW(n); // Check if the expanded path matches if (!str_prefix_matches((art_leaf*)n, str, str_len)) { found = (art_leaf*)n; func(found->key, found->key_len, found->key_len != (uint32_t)str_len, found->value, arg); } break; } // Check if current is leaf child = find_child(n, 0); m = (child) ? *child : NULL; if (m && IS_LEAF(m)) { m = (art_node*)LEAF_RAW(m); // Check if the expanded path matches if (!str_prefix_matches((art_leaf*)m, str, str_len)) { found = (art_leaf*)m; stop_search = func(found->key, found->key_len, found->key_len != (uint32_t)str_len, found->value, arg); } } // Bail if the prefix does not match if (n->partial_len) { prefix_len = check_prefix(n, str, str_len, depth); if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len)) break; depth = depth + n->partial_len; } // Recursively search child = find_child(n, str[depth]); n = (child) ? *child : NULL; depth++; } if (found == NULL) { return ; } return ; } ================================================ FILE: src/lib/bitops.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/lib/bitmap.h" #include "smartdns/lib/bitops.h" /* * This is a common helper function for find_next_bit, find_next_zero_bit, and * find_next_and_bit. The differences are: * - The "invert" argument, which is XORed with each fetched word before * searching it for one bits. * - The optional "addr2", which is addr2 with "addr1" if present. */ static inline unsigned long _find_next_bit(const unsigned long *addr1, const unsigned long *addr2, unsigned long nbits, unsigned long start, unsigned long invert) { unsigned long tmp; if (unlikely(start >= nbits)) return nbits; tmp = addr1[start / BITS_PER_LONG]; if (addr2) tmp &= addr2[start / BITS_PER_LONG]; tmp ^= invert; /* Handle 1st word. */ tmp &= BITMAP_FIRST_WORD_MASK(start); start = round_down(start, BITS_PER_LONG); while (!tmp) { start += BITS_PER_LONG; if (start >= nbits) return nbits; tmp = addr1[start / BITS_PER_LONG]; if (addr2) tmp &= addr2[start / BITS_PER_LONG]; tmp ^= invert; } return min(start + __ffs(tmp), nbits); } /* * Find the next set bit in a memory region. */ unsigned long find_next_bit(const unsigned long *addr, unsigned long size, unsigned long offset) { return _find_next_bit(addr, NULL, size, offset, 0UL); } /* * Find the first set bit in a memory region. */ unsigned long find_first_bit(const unsigned long *addr, unsigned long size) { unsigned long idx; for (idx = 0; idx * BITS_PER_LONG < size; idx++) { if (addr[idx]) return min(idx * BITS_PER_LONG + __ffs(addr[idx]), size); } return size; } /* * Find the first cleared bit in a memory region. */ unsigned long find_first_zero_bit(const unsigned long *addr, unsigned long size) { unsigned long idx; for (idx = 0; idx * BITS_PER_LONG < size; idx++) { if (addr[idx] != ~0UL) return min(idx * BITS_PER_LONG + ffz(addr[idx]), size); } return size; } unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size, unsigned long offset) { return _find_next_bit(addr, NULL, size, offset, ~0UL); } unsigned long find_next_and_bit(const unsigned long *addr1, const unsigned long *addr2, unsigned long size, unsigned long offset) { return _find_next_bit(addr1, addr2, size, offset, 0UL); } /** * hweightN - returns the hamming weight of a N-bit word * @x: the word to weigh * * The Hamming Weight of a number is the total number of bits set in it. */ unsigned int __sw_hweight32(unsigned int w) { #ifdef CONFIG_ARCH_HAS_FAST_MULTIPLIER w -= (w >> 1) & 0x55555555; w = (w & 0x33333333) + ((w >> 2) & 0x33333333); w = (w + (w >> 4)) & 0x0f0f0f0f; return (w * 0x01010101) >> 24; #else unsigned int res = w - ((w >> 1) & 0x55555555); res = (res & 0x33333333) + ((res >> 2) & 0x33333333); res = (res + (res >> 4)) & 0x0F0F0F0F; res = res + (res >> 8); return (res + (res >> 16)) & 0x000000FF; #endif } unsigned int __sw_hweight16(unsigned int w) { unsigned int res = w - ((w >> 1) & 0x5555); res = (res & 0x3333) + ((res >> 2) & 0x3333); res = (res + (res >> 4)) & 0x0F0F; return (res + (res >> 8)) & 0x00FF; } unsigned int __sw_hweight8(unsigned int w) { unsigned int res = w - ((w >> 1) & 0x55); res = (res & 0x33) + ((res >> 2) & 0x33); return (res + (res >> 4)) & 0x0F; } unsigned long __sw_hweight64(uint64_t w) { #if BITS_PER_LONG == 32 return __sw_hweight32((unsigned int)(w >> 32)) + __sw_hweight32((unsigned int)w); #elif BITS_PER_LONG == 64 #ifdef CONFIG_ARCH_HAS_FAST_MULTIPLIER w -= (w >> 1) & 0x5555555555555555ul; w = (w & 0x3333333333333333ul) + ((w >> 2) & 0x3333333333333333ul); w = (w + (w >> 4)) & 0x0f0f0f0f0f0f0f0ful; return (w * 0x0101010101010101ul) >> 56; #else uint64_t res = w - ((w >> 1) & 0x5555555555555555ul); res = (res & 0x3333333333333333ul) + ((res >> 2) & 0x3333333333333333ul); res = (res + (res >> 4)) & 0x0F0F0F0F0F0F0F0Ful; res = res + (res >> 8); res = res + (res >> 16); return (res + (res >> 32)) & 0x00000000000000FFul; #endif #endif return 0; } ================================================ FILE: src/lib/conf.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/lib/conf.h" #include #include #include #include #include #include #include #include static const char *current_conf_file = NULL; static int current_conf_lineno = 0; const char *conf_get_conf_file(void) { return current_conf_file; } int conf_get_current_lineno(void) { return current_conf_lineno; } static char *get_dir_name(char *path) { if (strstr(path, "/") == NULL) { strncpy(path, "./", PATH_MAX); return path; } return dirname(path); } const char *conf_get_conf_fullpath(const char *path, char *fullpath, size_t path_len) { char file_path_dir[PATH_MAX]; const char *conf_file = NULL; if (path_len < 1) { return NULL; } if (path[0] == '/') { strncpy(fullpath, path, path_len); return fullpath; } conf_file = conf_get_conf_file(); if (conf_file == NULL) { strncpy(fullpath, path, path_len); return fullpath; } strncpy(file_path_dir, conf_file, PATH_MAX - 1); file_path_dir[PATH_MAX - 1] = 0; get_dir_name(file_path_dir); if (file_path_dir[0] == '\0') { strncpy(fullpath, path, path_len); return fullpath; } if (snprintf(fullpath, PATH_MAX, "%s/%s", file_path_dir, path) < 0) { return NULL; } return fullpath; } int conf_custom(const char *item, void *data, int argc, char *argv[]) { struct config_item_custom *item_custom = data; return item_custom->custom_func(item_custom->custom_data, argc, argv); } int conf_int(const char *item, void *data, int argc, char *argv[]) { struct config_item_int *item_int = data; int value = 0; if (argc < 2) { return -1; } value = atoi(argv[1]); if (value < item_int->min) { value = item_int->min; } else if (value > item_int->max) { value = item_int->max; } if (item_int->func) { return item_int->func(value, item_int->data); } *(item_int->data) = value; return 0; } int conf_int_base(const char *item, void *data, int argc, char *argv[]) { struct config_item_int_base *item_int = data; int value = 0; if (argc < 2) { return -1; } value = strtol(argv[1], NULL, item_int->base); if (value < item_int->min) { value = item_int->min; } else if (value > item_int->max) { value = item_int->max; } if (item_int->func) { return item_int->func(value, item_int->data); } *(item_int->data) = value; return 0; } int conf_string(const char *item, void *data, int argc, char *argv[]) { struct config_item_string *item_string = data; if (argc < 2) { return -1; } if (item_string->func) { return item_string->func(argv[1], item_string->data); } strncpy(item_string->data, argv[1], item_string->size); return 0; } int conf_yesno(const char *item, void *data, int argc, char *argv[]) { struct config_item_yesno *item_yesno = data; int yes = 0; if (argc < 2) { return -1; } char *value = argv[1]; if (strncmp("auto", value, sizeof("auto")) == 0 || strncmp("AUTO", value, sizeof("AUTO")) == 0) { return 0; } if (strncmp("yes", value, sizeof("yes")) == 0 || strncmp("YES", value, sizeof("YES")) == 0) { yes = 1; } else if (strncmp("no", value, sizeof("no")) == 0 || strncmp("NO", value, sizeof("NO")) == 0) { yes = 0; } if (item_yesno->func) { return item_yesno->func(yes, item_yesno->data); } *(item_yesno->data) = yes; return 0; } int conf_size(const char *item, void *data, int argc, char *argv[]) { int base = 1; size_t size = 0; int num = 0; struct config_item_size *item_size = data; char *value = argv[1]; if (strstr(value, "k") || strstr(value, "K")) { base = 1024; } else if (strstr(value, "m") || strstr(value, "M")) { base = 1024 * 1024; } else if (strstr(value, "g") || strstr(value, "G")) { base = 1024 * 1024 * 1024; } num = atoi(value); if (num < 0) { return -1; } size = (size_t)num * base; if (size > item_size->max) { size = item_size->max; } else if (size < item_size->min) { size = item_size->min; } if (item_size->func) { return item_size->func(size, item_size->data); } *(item_size->data) = size; return 0; } int conf_ssize(const char *item, void *data, int argc, char *argv[]) { int base = 1; ssize_t size = 0; int num = 0; struct config_item_ssize *item_size = data; char *value = argv[1]; if (strstr(value, "k") || strstr(value, "K")) { base = 1024; } else if (strstr(value, "m") || strstr(value, "M")) { base = 1024 * 1024; } else if (strstr(value, "g") || strstr(value, "G")) { base = 1024 * 1024 * 1024; } num = atoi(value); size = (ssize_t)num * base; if (size > item_size->max) { size = item_size->max; } else if (size < item_size->min) { size = item_size->min; } if (item_size->func) { return item_size->func(size, item_size->data); } *(item_size->data) = size; return 0; } int conf_enum(const char *item, void *data, int argc, char *argv[]) { struct config_enum *item_enum = data; char *enum_name = argv[1]; int i = 0; if (argc <= 0) { return -1; } for (i = 0; item_enum->list[i].name != NULL; i++) { if (strcmp(enum_name, item_enum->list[i].name) == 0) { if (item_enum->func) { return item_enum->func(item_enum->list[i].id, item_enum->data); } *(item_enum->data) = item_enum->list[i].id; return 0; } } printf("Not found config value '%s', valid value is:\n", enum_name); for (i = 0; item_enum->list[i].name != NULL; i++) { printf(" %s\n", item_enum->list[i].name); } return -1; } void conf_getopt_reset(void) { static struct option long_options[] = {{"-", 0, NULL, 0}, {NULL, 0, NULL, 0}}; int argc = 2; char *argv[3] = {"reset", "", NULL}; optind = 0; opterr = 0; optopt = 0; getopt_long(argc, argv, "", long_options, NULL); optind = 0; opterr = 0; optopt = 0; } int conf_parse_key_values(char *line, int *key_num, char **keys, char **values) { int count = 0; char *ptr = line; char *key = NULL; char *value = NULL; char *field_start = NULL; int filed_stage = 0; int inquote = 0; int end = 0; if (line == NULL || key_num == NULL || keys == NULL || values == NULL) { return -1; } while (1) { if (*ptr == '\'' || *ptr == '"') { if (inquote == 0) { inquote = *ptr; ptr++; continue; } else if (inquote == *ptr) { inquote = 0; *ptr = '\0'; } } if (field_start == NULL) { field_start = ptr; } if (inquote != 0) { ptr++; continue; } if (*ptr == ',' || *ptr == '=' || *ptr == '\0') { if (filed_stage == 0) { key = field_start; if (*key == '\0' || *key == ',') { field_start = NULL; if (end == 1) { break; } ptr++; continue; } value = ptr; filed_stage = 1; keys[count] = key; values[count] = value; if (*ptr == '\0' || *ptr == ',') { count++; key = NULL; value = NULL; filed_stage = 0; } *ptr = '\0'; } else if (filed_stage == 1) { value = field_start; if (*ptr == '=') { goto errout; } filed_stage = 0; keys[count] = key; values[count] = value; count++; *ptr = '\0'; key = NULL; value = NULL; } field_start = NULL; } if (end == 1) { break; } ptr++; if (*ptr == '\0') { end = 1; } } *key_num = count; return 0; errout: return -1; } static int conf_parse_args(char *key, char *value, int *argc, char **argv) { char *start = NULL; char *ptr = value; int count = 0; int sep_flag = ' '; argv[0] = key; count++; while (*ptr != '\0') { if (*ptr == '\\') { char *tmp = ptr + 1; while (*tmp != '\0') { *(tmp - 1) = *tmp; tmp++; } *(tmp - 1) = '\0'; ptr++; continue; } if ((*ptr == '"' || *ptr == '\'') && start == NULL) { sep_flag = *ptr; start = NULL; } if (*ptr != sep_flag && start == NULL) { start = ptr; ptr++; continue; } if (*ptr == sep_flag && start == NULL) { ptr++; continue; } if (*ptr == sep_flag && start) { argv[count] = start; *ptr = '\0'; ptr++; count++; sep_flag = ' '; start = NULL; continue; } ptr++; } if (start != ptr && start) { argv[count] = start; count++; } *argc = count; argv[count] = NULL; return 0; } void load_exit(void) {} static int load_conf_printf(const char *key, const char *value, const char *file, int lineno, int ret) { if (ret != CONF_RET_OK) { printf("process config file '%s' failed at line %d.", file, lineno); if (ret == CONF_RET_ERR || ret == CONF_RET_NOENT) { return -1; } return 0; } return 0; } static int load_conf_file(const char *file, const struct config_item *items, conf_error_handler handler) { FILE *fp = NULL; char line[MAX_LINE_LEN + MAX_KEY_LEN]; char key[MAX_KEY_LEN]; char value[MAX_LINE_LEN]; int value_begin = 0, value_end = 0; int value_len = 0; int filed_num = 0; int i = 0; int last_item_index = -1; int argc = 0; char *argv[1024]; int ret = 0; int call_ret = 0; int line_no = 0; int line_len = 0; int read_len = 0; int is_last_line_wrap = 0; int current_line_wrap = 0; int is_func_found = 0; const char *last_file = NULL; if (handler == NULL) { handler = load_conf_printf; } fp = fopen(file, "r"); if (fp == NULL) { fprintf(stderr, "open config file '%s' failed, %s\n", file, strerror(errno)); return -1; } line_no = 0; while (fgets(line + line_len, MAX_LINE_LEN - line_len, fp)) { current_line_wrap = 0; line_no++; read_len = strnlen(line + line_len, sizeof(line)); if (read_len >= 2 && *(line + line_len + read_len - 2) == '\\') { read_len -= 1; current_line_wrap = 1; } /* comment in wrap line, skip */ if (*(line + line_len) == '#') { line_len = 0; continue; } /* trim prefix spaces in wrap line */ if ((current_line_wrap == 1 || is_last_line_wrap == 1) && read_len > 0) { is_last_line_wrap = current_line_wrap; read_len -= 1; for (i = 0; i < read_len; i++) { char *ptr = line + line_len + i; if (*ptr == ' ' || *ptr == '\t') { continue; } memmove(line + line_len, ptr, read_len - i + 1); line_len += read_len - i; break; } line[line_len] = '\0'; if (current_line_wrap) { continue; } } line_len = 0; is_last_line_wrap = 0; key[0] = '\0'; value[0] = '\0'; filed_num = sscanf(line, "%63s %n%4095[^\r\n]%n", key, &value_begin, value, &value_end); if (filed_num <= 0) { continue; } /* remove suffix space of value */ value_len = value_end - value_begin; if (value_len < 0) { continue; } for (i = value_len - 1; i >= 0; i--) { if (value[i] == ' ' || value[i] == '\t') { value[i] = '\0'; value_len--; } else { break; } } /* comment, skip */ if (key[0] == '#') { continue; } /* if field format is not key = value, error */ if (filed_num != 2 && filed_num != 1) { handler(NULL, NULL, file, line_no, CONF_RET_BADCONF); goto errout; } is_func_found = 0; for (i = last_item_index;; i++) { if (i < 0) { continue; } if (items[i].item == NULL) { break; } if (strncmp(items[i].item, key, MAX_KEY_LEN) != 0) { if (last_item_index >= 0) { i = -1; last_item_index = -1; } continue; } if (conf_parse_args(key, value, &argc, argv) != 0) { continue; } conf_getopt_reset(); /* call item function */ last_file = current_conf_file; current_conf_file = file; current_conf_lineno = line_no; call_ret = items[i].item_func(items[i].item, items[i].data, argc, argv); ret = handler(key, value, file, line_no, call_ret); if (ret != 0) { conf_getopt_reset(); goto errout; } conf_getopt_reset(); if (last_file) { current_conf_file = last_file; } last_item_index = i; is_func_found = 1; break; } if (is_func_found == 0) { if (handler(key, value, file, line_no, CONF_RET_NOENT) != 0) { goto errout; } } } fclose(fp); return 0; errout: if (fp) { fclose(fp); } return -1; } int load_conf(const char *file, const struct config_item items[], conf_error_handler handler) { return load_conf_file(file, items, handler); } ================================================ FILE: src/lib/idna.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/lib/idna.h" #include static unsigned _utf8_decode_slow(const char **p, const char *pe, unsigned a) { unsigned b; unsigned c; unsigned d; unsigned min; if (a > 0xF7) { return -1; } switch (pe - *p) { default: if (a > 0xEF) { min = 0x10000; a = a & 7; b = (unsigned char)*(*p)++; c = (unsigned char)*(*p)++; d = (unsigned char)*(*p)++; break; } case 2: if (a > 0xDF) { min = 0x800; b = 0x80 | (a & 15); c = (unsigned char)*(*p)++; d = (unsigned char)*(*p)++; a = 0; break; } case 1: if (a > 0xBF) { min = 0x80; b = 0x80; c = 0x80 | (a & 31); d = (unsigned char)*(*p)++; a = 0; break; } case 0: return -1; } if (0x80 != (0xC0 & (b ^ c ^ d))) { return -1; } b &= 63; c &= 63; d &= 63; a = (a << 18) | (b << 12) | (c << 6) | d; if (a < min) { return -1; } if (a > 0x10FFFF) { return -1; } if (a >= 0xD800 && a <= 0xDFFF) { return -1; } return a; } static unsigned _utf8_decode(const char **p, const char *pe) { unsigned a; a = (unsigned char)*(*p)++; if (a < 128) { return a; } return _utf8_decode_slow(p, pe, a); } static int _utf8_to_punycode_label(const char *s, const char *se, char **d, const char *de) { static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz0123456789"; const char *ss; unsigned c; unsigned h; unsigned k; unsigned n; unsigned m; unsigned q; unsigned t; unsigned x; unsigned y; unsigned bias; unsigned delta; unsigned todo; int first; h = 0; ss = s; todo = 0; while (s < se) { c = _utf8_decode(&s, se); if (c == UINT_MAX) { return -1; } if (c < 128) { h++; } else { todo++; } } if (todo > 0) { if (*d < de) { *(*d)++ = 'x'; } if (*d < de) { *(*d)++ = 'n'; } if (*d < de) { *(*d)++ = '-'; } if (*d < de) { *(*d)++ = '-'; } } x = 0; s = ss; while (s < se) { c = _utf8_decode(&s, se); if (c > 127) { continue; } if (*d < de) { *(*d)++ = c; } if (++x == h) { break; } } if (todo == 0) { return h; } if (h > 0) { if (*d < de) { *(*d)++ = '-'; } } n = 128; bias = 72; delta = 0; first = 1; while (todo > 0) { m = -1; s = ss; while (s < se) { c = _utf8_decode(&s, se); if (c >= n) { if (c < m) { m = c; } } } x = m - n; y = h + 1; if (x > ~delta / y) { return -1; } delta += x * y; n = m; s = ss; while (s < se) { c = _utf8_decode(&s, se); if (c < n) { if (++delta == 0) { return -1; } } if (c != n) { continue; } for (k = 36, q = delta;; k += 36) { t = 1; if (k > bias) { t = k - bias; } if (t > 26) { t = 26; } if (q < t) { break; } x = q - t; y = 36 - t; q = x / y; t = t + x % y; if (*d < de) { *(*d)++ = alphabet[t]; } } if (*d < de) { *(*d)++ = alphabet[q]; } delta /= 2; if (first) { delta /= 350; first = 0; } h++; delta += delta / h; for (bias = 0; delta > 35 * 26 / 2; bias += 36) { delta /= 35; } bias += 36 * delta / (delta + 38); delta = 0; todo--; } delta++; n++; } return 0; } int utf8_to_punycode(const char *src, int src_len, char *dst, int dst_len) { const char *si; const char *se; const char *st; unsigned c; char *ds; char *de; int rc; ds = dst; si = src; se = src + src_len; de = dst + dst_len; while (si < se) { st = si; c = _utf8_decode(&si, se); if (c == UINT_MAX) { return -1; } if (c != '.') { if (c != 0x3002) { if (c != 0xFF0E) { if (c != 0xFF61) { continue; } } } } rc = _utf8_to_punycode_label(src, st, &dst, de); if (rc < 0) { return rc; } if (dst < de) { *dst++ = '.'; } src = si; } if (src < se) { rc = _utf8_to_punycode_label(src, se, &dst, de); if (rc < 0) { return rc; } } if (dst < de) { *dst++ = '\0'; } return dst - ds; } ================================================ FILE: src/lib/radix.c ================================================ /* * Copyright (c) 1999-2000 * * The Regents of the University of Michigan ("The Regents") and * Merit Network, Inc. All rights reserved. Redistribution and use * in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. All advertising materials mentioning features or use of * this software must display the following acknowledgement: * * This product includes software developed by the University of * Michigan, Merit Network, Inc., and their contributors. * * 4. Neither the name of the University, Merit Network, nor the * names of their contributors may be used to endorse or * promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TH E REGENTS * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HO WEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Portions Copyright (c) 2004,2005 Damien Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _GNU_SOURCE #include #include #include #include #include "smartdns/lib/radix.h" /* $Id: radix.c,v 1.17 2007/10/24 06:04:31 djm Exp $ */ /* * Originally from MRT include/defs.h * $MRTId: defs.h,v 1.1.1.1 2000/08/14 18:46:10 labovit Exp $ */ #define BIT_TEST(f, b) ((f) & (b)) /* * Originally from MRT include/mrt.h * $MRTId: mrt.h,v 1.1.1.1 2000/08/14 18:46:10 labovit Exp $ */ #define prefix_tochar(prefix) ((char *)&(prefix)->add) #define prefix_touchar(prefix) ((unsigned char *)&(prefix)->add) /* * Originally from MRT lib/mrt/prefix.c * $MRTId: prefix.c,v 1.1.1.1 2000/08/14 18:46:11 labovit Exp $ */ static int comp_with_mask(unsigned char *addr, unsigned char *dest, unsigned int mask) { if (memcmp(addr, dest, mask / 8) == 0) { unsigned int n = mask / 8; unsigned int m = ((unsigned int)(~0) << (8 - (mask % 8))); if (mask % 8 == 0 || (addr[n] & m) == (dest[n] & m)) return (1); } return (0); } static prefix_t *New_Prefix2(int family, void *dest, int bitlen, prefix_t *prefix) { int dynamic_allocated = 0; int default_bitlen = 32; if (family == AF_INET6) { default_bitlen = 128; if (prefix == NULL) { if ((prefix = calloc(1, sizeof(*prefix))) == NULL) return (NULL); dynamic_allocated++; } memcpy(&prefix->add.sin6, dest, 16); } else if (family == AF_INET) { if (prefix == NULL) { if ((prefix = calloc(1, sizeof(*prefix))) == NULL) return (NULL); dynamic_allocated++; } memcpy(&prefix->add.sin, dest, 4); } else return (NULL); prefix->bitlen = (bitlen >= 0) ? bitlen : default_bitlen; prefix->family = family; prefix->ref_count = 0; if (dynamic_allocated) prefix->ref_count++; return (prefix); } static prefix_t *Ref_Prefix(prefix_t *prefix) { if (prefix == NULL) return (NULL); if (prefix->ref_count == 0) { /* make a copy in case of a static prefix */ return (New_Prefix2(prefix->family, &prefix->add, prefix->bitlen, NULL)); } prefix->ref_count++; return (prefix); } void Deref_Prefix(prefix_t *prefix) { if (prefix == NULL) return; prefix->ref_count--; if (prefix->ref_count <= 0) { free(prefix); return; } } /* * Originally from MRT lib/radix/radix.c * $MRTId: radix.c,v 1.1.1.1 2000/08/14 18:46:13 labovit Exp $ */ /* these routines support continuous mask only */ radix_tree_t *New_Radix(void) { radix_tree_t *radix; if ((radix = calloc(1, sizeof(*radix))) == NULL) return (NULL); radix->maxbits = 128; radix->head = NULL; radix->num_active_node = 0; return (radix); } /* * if func is supplied, it will be called as func(node->data) * before deleting the node */ static void Clear_Radix(radix_tree_t *radix, rdx_cb_t func, void *cbctx) { if (radix->head) { radix_node_t *Xstack[RADIX_MAXBITS + 1]; radix_node_t **Xsp = Xstack; radix_node_t *Xrn = radix->head; while (Xrn) { radix_node_t *l = Xrn->l; radix_node_t *r = Xrn->r; if (Xrn->prefix) { Deref_Prefix(Xrn->prefix); if (Xrn->data && func) func(Xrn, cbctx); } free(Xrn); radix->num_active_node--; if (l) { if (r) *Xsp++ = r; Xrn = l; } else if (r) { Xrn = r; } else if (Xsp != Xstack) { Xrn = *(--Xsp); } else { Xrn = (radix_node_t *) 0; } } } } void Destroy_Radix(radix_tree_t *radix, rdx_cb_t func, void *cbctx) { if (radix == NULL) { return; } Clear_Radix(radix, func, cbctx); free(radix); } /* * if func is supplied, it will be called as func(node->prefix, node->data) */ void radix_process(radix_tree_t *radix, rdx_cb_t func, void *cbctx) { radix_node_t *node; RADIX_WALK(radix->head, node) { func(node, cbctx); } RADIX_WALK_END; } radix_node_t *radix_search_exact(radix_tree_t *radix, prefix_t *prefix) { radix_node_t *node; unsigned char *addr; unsigned int bitlen; if (radix->head == NULL) return (NULL); node = radix->head; addr = prefix_touchar(prefix); bitlen = prefix->bitlen; while (node->bit < bitlen) { if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) node = node->r; else node = node->l; if (node == NULL) return (NULL); } if (node->bit > bitlen || node->prefix == NULL) return (NULL); if (comp_with_mask(prefix_touchar(node->prefix), prefix_touchar(prefix), bitlen)) return (node); return (NULL); } /* if inclusive != 0, "best" may be the given prefix itself */ static radix_node_t *radix_search_best2(radix_tree_t *radix, prefix_t *prefix, int inclusive) { radix_node_t *node; radix_node_t *stack[RADIX_MAXBITS + 1] = {0}; unsigned char *addr; unsigned int bitlen; int cnt = 0; if (radix->head == NULL) return (NULL); node = radix->head; addr = prefix_touchar(prefix); bitlen = prefix->bitlen; while (node->bit < bitlen) { if (node->prefix) stack[cnt++] = node; if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) node = node->r; else node = node->l; if (node == NULL) break; } if (inclusive && node && node->prefix) stack[cnt++] = node; if (cnt <= 0) return (NULL); while (--cnt >= 0) { node = stack[cnt]; if (comp_with_mask(prefix_touchar(node->prefix), prefix_touchar(prefix), node->prefix->bitlen)) return (node); } return (NULL); } radix_node_t *radix_search_best(radix_tree_t *radix, prefix_t *prefix) { return (radix_search_best2(radix, prefix, 1)); } radix_node_t *radix_lookup(radix_tree_t *radix, prefix_t *prefix) { radix_node_t *node, *new_node, *parent, *glue; unsigned char *addr, *test_addr; unsigned int bitlen, check_bit, differ_bit; unsigned int i, j, r; if (radix->head == NULL) { if ((node = calloc(1, sizeof(*node))) == NULL) return (NULL); node->bit = prefix->bitlen; node->prefix = Ref_Prefix(prefix); node->parent = NULL; node->l = node->r = NULL; node->data = NULL; radix->head = node; radix->num_active_node++; return (node); } addr = prefix_touchar(prefix); bitlen = prefix->bitlen; node = radix->head; while (node->bit < bitlen || node->prefix == NULL) { if (node->bit < radix->maxbits && BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { if (node->r == NULL) break; node = node->r; } else { if (node->l == NULL) break; node = node->l; } } test_addr = prefix_touchar(node->prefix); /* find the first bit different */ check_bit = (node->bit < bitlen) ? node->bit : bitlen; differ_bit = 0; for (i = 0; i * 8 < check_bit; i++) { if ((r = (addr[i] ^ test_addr[i])) == 0) { differ_bit = (i + 1) * 8; continue; } /* I know the better way, but for now */ for (j = 0; j < 8; j++) { if (BIT_TEST(r, (0x80 >> j))) break; } /* must be found */ differ_bit = i * 8 + j; break; } if (differ_bit > check_bit) differ_bit = check_bit; parent = node->parent; while (parent && parent->bit >= differ_bit) { node = parent; parent = node->parent; } if (differ_bit == bitlen && node->bit == bitlen) { if (node->prefix == NULL) node->prefix = Ref_Prefix(prefix); return (node); } if ((new_node = calloc(1, sizeof(*new_node))) == NULL) return (NULL); new_node->bit = prefix->bitlen; new_node->prefix = Ref_Prefix(prefix); new_node->parent = NULL; new_node->l = new_node->r = NULL; new_node->data = NULL; radix->num_active_node++; if (node->bit == differ_bit) { new_node->parent = node; if (node->bit < radix->maxbits && BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) node->r = new_node; else node->l = new_node; return (new_node); } if (bitlen == differ_bit) { if (bitlen < radix->maxbits && BIT_TEST(test_addr[bitlen >> 3], 0x80 >> (bitlen & 0x07))) new_node->r = node; else new_node->l = node; new_node->parent = node->parent; if (node->parent == NULL) radix->head = new_node; else if (node->parent->r == node) node->parent->r = new_node; else node->parent->l = new_node; node->parent = new_node; } else { if ((glue = calloc(1, sizeof(*glue))) == NULL) { free(new_node); return (NULL); } glue->bit = differ_bit; glue->prefix = NULL; glue->parent = node->parent; glue->data = NULL; radix->num_active_node++; if (differ_bit < radix->maxbits && BIT_TEST(addr[differ_bit >> 3], 0x80 >> (differ_bit & 0x07))) { glue->r = new_node; glue->l = node; } else { glue->r = node; glue->l = new_node; } new_node->parent = glue; if (node->parent == NULL) radix->head = glue; else if (node->parent->r == node) node->parent->r = glue; else node->parent->l = glue; node->parent = glue; } return (new_node); } void radix_remove(radix_tree_t *radix, radix_node_t *node) { radix_node_t *parent, *child; if (node->r && node->l) { /* * this might be a placeholder node -- have to check and make * sure there is a prefix aossciated with it ! */ if (node->prefix != NULL) Deref_Prefix(node->prefix); node->prefix = NULL; /* Also I needed to clear data pointer -- masaki */ node->data = NULL; return; } if (node->r == NULL && node->l == NULL) { parent = node->parent; Deref_Prefix(node->prefix); free(node); radix->num_active_node--; if (parent == NULL) { radix->head = NULL; return; } if (parent->r == node) { parent->r = NULL; child = parent->l; } else { parent->l = NULL; child = parent->r; } if (parent->prefix) return; /* we need to remove parent too */ if (parent->parent == NULL) radix->head = child; else if (parent->parent->r == parent) parent->parent->r = child; else parent->parent->l = child; child->parent = parent->parent; free(parent); radix->num_active_node--; return; } if (node->r) child = node->r; else child = node->l; parent = node->parent; child->parent = parent; Deref_Prefix(node->prefix); free(node); radix->num_active_node--; if (parent == NULL) { radix->head = child; return; } if (parent->r == node) parent->r = child; else parent->l = child; } /* Local additions */ static void sanitise_mask(unsigned char *addr, unsigned int masklen, unsigned int maskbits) { unsigned int i = masklen / 8; unsigned int j = masklen % 8; if (j != 0) { addr[i] &= (unsigned int)(~0) << (8 - j); i++; } for (; i < maskbits / 8; i++) addr[i] = 0; } static void update_addr(const char **addr_ptr, const char *addr, size_t len) { const char *ipv6_prefix = "::ffff:0:0"; if (strncmp(addr, "::ffff", len) == 0) *addr_ptr = ipv6_prefix; else *addr_ptr = (char *)addr; } prefix_t *prefix_pton(const char *string, long len, prefix_t *prefix, const char **errmsg) { static char save[256]; char *cp, *ep; struct addrinfo hints, *ai; void *addr; const char *addr_str = NULL; prefix_t *ret; size_t slen; int r; ret = NULL; /* Copy the string to parse, because we modify it */ if ((slen = strlen(string) + 1) > sizeof(save)) { *errmsg = "string too long"; return (NULL); } memcpy(save, string, slen); if ((cp = strchr(save, '/')) != NULL) { if (len != -1 ) { *errmsg = "masklen specified twice"; return (NULL); } *cp++ = '\0'; len = strtol(cp, &ep, 10); if (*cp == '\0' || *ep != '\0' || len < 0) { *errmsg = "could not parse masklen"; return (NULL); } /* More checks below */ } memset(&hints, '\0', sizeof(hints)); hints.ai_flags = AI_NUMERICHOST; update_addr(&addr_str, save, slen); if ((r = getaddrinfo(addr_str, NULL, &hints, &ai)) != 0) { snprintf(save, sizeof(save), "getaddrinfo: %s:", gai_strerror(r)); *errmsg = save; return NULL; } if (ai == NULL || ai->ai_addr == NULL) { *errmsg = "getaddrinfo returned no result"; goto out; } switch (ai->ai_addr->sa_family) { case AF_INET: if (len == -1) len = 32; else if (len < 0 || len > 32) goto out; addr = &((struct sockaddr_in *) ai->ai_addr)->sin_addr; sanitise_mask(addr, len, 32); break; case AF_INET6: if (len == -1) len = 128; else if (len < 0 || len > 128) goto out; addr = &((struct sockaddr_in6 *) ai->ai_addr)->sin6_addr; sanitise_mask(addr, len, 128); break; default: goto out; } ret = New_Prefix2(ai->ai_addr->sa_family, addr, len, prefix); if (ret == NULL) *errmsg = "New_Prefix2 failed"; out: freeaddrinfo(ai); return (ret); } prefix_t *prefix_from_blob(unsigned char *blob, int len, int prefixlen, prefix_t *prefix) { int family, maxprefix; switch (len) { case 4: /* Assume AF_INET */ family = AF_INET; maxprefix = 32; break; case 16: /* Assume AF_INET6 */ family = AF_INET6; maxprefix = 128; break; default: /* Who knows? */ return NULL; } if (prefixlen == -1) prefixlen = maxprefix; if (prefixlen < 0 || prefixlen > maxprefix) return NULL; return (New_Prefix2(family, blob, prefixlen, prefix)); } const char * prefix_addr_ntop(prefix_t *prefix, char *buf, size_t len) { return (inet_ntop(prefix->family, &prefix->add, buf, len)); } const char * prefix_ntop(prefix_t *prefix, char *buf, size_t len) { char addrbuf[128]; if (prefix_addr_ntop(prefix, addrbuf, sizeof(addrbuf)) == NULL) return (NULL); snprintf(buf, len, "%s/%d", addrbuf, prefix->bitlen); return (buf); } ================================================ FILE: src/lib/rbtree.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/lib/rbtree.h" #include static inline void rb_set_black(struct rb_node *rb) { rb->__rb_parent_color |= RB_BLACK; } static inline struct rb_node *rb_red_parent(struct rb_node *red) { return (struct rb_node *)red->__rb_parent_color; } /* * Helper function for rotations: * - old's parent and color get assigned to new * - old gets assigned new as a parent and 'color' as a color. */ static inline void __rb_rotate_set_parents(struct rb_node *old, struct rb_node *new, struct rb_root *root, int color) { struct rb_node *parent = rb_parent(old); new->__rb_parent_color = old->__rb_parent_color; rb_set_parent_color(old, new, color); __rb_change_child(old, new, parent, root); } static inline void __rb_insert(struct rb_node *node, struct rb_root *root, void (*augment_rotate)(struct rb_node *old, struct rb_node *new)) { struct rb_node *parent = rb_red_parent(node), *gparent, *tmp; while (true) { /* * Loop invariant: node is red * * If there is a black parent, we are done. * Otherwise, take some corrective action as we don't * want a red root or two consecutive red nodes. */ if (!parent) { rb_set_parent_color(node, NULL, RB_BLACK); break; } else if (rb_is_black(parent)) break; gparent = rb_red_parent(parent); tmp = gparent->rb_right; if (parent != tmp) { /* parent == gparent->rb_left */ if (tmp && rb_is_red(tmp)) { /* * Case 1 - color flips * * G g * / \ / \ * p u --> P U * / / * n n * * However, since g's parent might be red, and * 4) does not allow this, we need to recurse * at g. */ rb_set_parent_color(tmp, gparent, RB_BLACK); rb_set_parent_color(parent, gparent, RB_BLACK); node = gparent; parent = rb_parent(node); rb_set_parent_color(node, parent, RB_RED); continue; } tmp = parent->rb_right; if (node == tmp) { /* * Case 2 - left rotate at parent * * G G * / \ / \ * p U --> n U * \ / * n p * * This still leaves us in violation of 4), the * continuation into Case 3 will fix that. */ parent->rb_right = tmp = node->rb_left; node->rb_left = parent; if (tmp) rb_set_parent_color(tmp, parent, RB_BLACK); rb_set_parent_color(parent, node, RB_RED); augment_rotate(parent, node); parent = node; tmp = node->rb_right; } /* * Case 3 - right rotate at gparent * * G P * / \ / \ * p U --> n g * / \ * n U */ gparent->rb_left = tmp; /* == parent->rb_right */ parent->rb_right = gparent; if (tmp) rb_set_parent_color(tmp, gparent, RB_BLACK); __rb_rotate_set_parents(gparent, parent, root, RB_RED); augment_rotate(gparent, parent); break; } else { tmp = gparent->rb_left; if (tmp && rb_is_red(tmp)) { /* Case 1 - color flips */ rb_set_parent_color(tmp, gparent, RB_BLACK); rb_set_parent_color(parent, gparent, RB_BLACK); node = gparent; parent = rb_parent(node); rb_set_parent_color(node, parent, RB_RED); continue; } tmp = parent->rb_left; if (node == tmp) { /* Case 2 - right rotate at parent */ parent->rb_left = tmp = node->rb_right; node->rb_right = parent; if (tmp) rb_set_parent_color(tmp, parent, RB_BLACK); rb_set_parent_color(parent, node, RB_RED); augment_rotate(parent, node); parent = node; tmp = node->rb_left; } /* Case 3 - left rotate at gparent */ gparent->rb_right = tmp; /* == parent->rb_left */ parent->rb_left = gparent; if (tmp) rb_set_parent_color(tmp, gparent, RB_BLACK); __rb_rotate_set_parents(gparent, parent, root, RB_RED); augment_rotate(gparent, parent); break; } } } /* * Inline version for rb_erase() use - we want to be able to inline * and eliminate the dummy_rotate callback there */ static inline void ____rb_erase_color(struct rb_node *parent, struct rb_root *root, void (*augment_rotate)(struct rb_node *old, struct rb_node *new)) { struct rb_node *node = NULL, *sibling, *tmp1, *tmp2; while (true) { /* * Loop invariants: * - node is black (or NULL on first iteration) * - node is not the root (parent is not NULL) * - All leaf paths going through parent and node have a * black node count that is 1 lower than other leaf paths. */ sibling = parent->rb_right; if (node != sibling) { /* node == parent->rb_left */ if (rb_is_red(sibling)) { /* * Case 1 - left rotate at parent * * P S * / \ / \ * N s --> p Sr * / \ / \ * Sl Sr N Sl */ parent->rb_right = tmp1 = sibling->rb_left; sibling->rb_left = parent; rb_set_parent_color(tmp1, parent, RB_BLACK); __rb_rotate_set_parents(parent, sibling, root, RB_RED); augment_rotate(parent, sibling); sibling = tmp1; } tmp1 = sibling->rb_right; if (!tmp1 || rb_is_black(tmp1)) { tmp2 = sibling->rb_left; if (!tmp2 || rb_is_black(tmp2)) { /* * Case 2 - sibling color flip * (p could be either color here) * * (p) (p) * / \ / \ * N S --> N s * / \ / \ * Sl Sr Sl Sr * * This leaves us violating 5) which * can be fixed by flipping p to black * if it was red, or by recursing at p. * p is red when coming from Case 1. */ rb_set_parent_color(sibling, parent, RB_RED); if (rb_is_red(parent)) rb_set_black(parent); else { node = parent; parent = rb_parent(node); if (parent) continue; } break; } /* * Case 3 - right rotate at sibling * (p could be either color here) * * (p) (p) * / \ / \ * N S --> N Sl * / \ \ * sl Sr s * \ * Sr */ sibling->rb_left = tmp1 = tmp2->rb_right; tmp2->rb_right = sibling; parent->rb_right = tmp2; if (tmp1) rb_set_parent_color(tmp1, sibling, RB_BLACK); augment_rotate(sibling, tmp2); tmp1 = sibling; sibling = tmp2; } /* * Case 4 - left rotate at parent + color flips * (p and sl could be either color here. * After rotation, p becomes black, s acquires * p's color, and sl keeps its color) * * (p) (s) * / \ / \ * N S --> P Sr * / \ / \ * (sl) sr N (sl) */ parent->rb_right = tmp2 = sibling->rb_left; sibling->rb_left = parent; rb_set_parent_color(tmp1, sibling, RB_BLACK); if (tmp2) rb_set_parent(tmp2, parent); __rb_rotate_set_parents(parent, sibling, root, RB_BLACK); augment_rotate(parent, sibling); break; } else { sibling = parent->rb_left; if (rb_is_red(sibling)) { /* Case 1 - right rotate at parent */ parent->rb_left = tmp1 = sibling->rb_right; sibling->rb_right = parent; rb_set_parent_color(tmp1, parent, RB_BLACK); __rb_rotate_set_parents(parent, sibling, root, RB_RED); augment_rotate(parent, sibling); sibling = tmp1; } tmp1 = sibling->rb_left; if (!tmp1 || rb_is_black(tmp1)) { tmp2 = sibling->rb_right; if (!tmp2 || rb_is_black(tmp2)) { /* Case 2 - sibling color flip */ rb_set_parent_color(sibling, parent, RB_RED); if (rb_is_red(parent)) rb_set_black(parent); else { node = parent; parent = rb_parent(node); if (parent) continue; } break; } /* Case 3 - right rotate at sibling */ sibling->rb_right = tmp1 = tmp2->rb_left; tmp2->rb_left = sibling; parent->rb_left = tmp2; if (tmp1) rb_set_parent_color(tmp1, sibling, RB_BLACK); augment_rotate(sibling, tmp2); tmp1 = sibling; sibling = tmp2; } /* Case 4 - left rotate at parent + color flips */ parent->rb_left = tmp2 = sibling->rb_right; sibling->rb_right = parent; rb_set_parent_color(tmp1, sibling, RB_BLACK); if (tmp2) rb_set_parent(tmp2, parent); __rb_rotate_set_parents(parent, sibling, root, RB_BLACK); augment_rotate(parent, sibling); break; } } } /* Non-inline version for rb_erase_augmented() use */ void __rb_erase_color(struct rb_node *parent, struct rb_root *root, void (*augment_rotate)(struct rb_node *old, struct rb_node *new)) { ____rb_erase_color(parent, root, augment_rotate); } /* * Non-augmented rbtree manipulation functions. * * We use dummy augmented callbacks here, and have the compiler optimize them * out of the rb_insert_color() and rb_erase() function definitions. */ static inline void dummy_propagate(struct rb_node *node, struct rb_node *stop) {} static inline void dummy_copy(struct rb_node *old, struct rb_node *new) {} static inline void dummy_rotate(struct rb_node *old, struct rb_node *new) {} static const struct rb_augment_callbacks dummy_callbacks = { dummy_propagate, dummy_copy, dummy_rotate }; void rb_insert_color(struct rb_node *node, struct rb_root *root) { __rb_insert(node, root, dummy_rotate); } void rb_erase(struct rb_node *node, struct rb_root *root) { struct rb_node *rebalance; rebalance = __rb_erase_augmented(node, root, &dummy_callbacks); if (rebalance) ____rb_erase_color(rebalance, root, dummy_rotate); } /* * Augmented rbtree manipulation functions. * * This instantiates the same inline functions as in the non-augmented * case, but this time with user-defined callbacks. */ void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, void (*augment_rotate)(struct rb_node *old, struct rb_node *new)) { __rb_insert(node, root, augment_rotate); } /* * This function returns the first node (in sort order) of the tree. */ struct rb_node *rb_first(const struct rb_root *root) { struct rb_node *n; n = root->rb_node; if (!n) return NULL; while (n->rb_left) n = n->rb_left; return n; } struct rb_node *rb_last(const struct rb_root *root) { struct rb_node *n; n = root->rb_node; if (!n) return NULL; while (n->rb_right) n = n->rb_right; return n; } struct rb_node *rb_next(const struct rb_node *node) { struct rb_node *parent; if (RB_EMPTY_NODE(node)) return NULL; /* * If we have a right-hand child, go down and then left as far * as we can. */ if (node->rb_right) { node = node->rb_right; while (node->rb_left) node=node->rb_left; return (struct rb_node *)node; } /* * No right-hand children. Everything down and left is smaller than us, * so any 'next' node must be in the general direction of our parent. * Go up the tree; any time the ancestor is a right-hand child of its * parent, keep going up. First time it's a left-hand child of its * parent, said parent is our 'next' node. */ while ((parent = rb_parent(node)) && node == parent->rb_right) node = parent; return parent; } struct rb_node *rb_prev(const struct rb_node *node) { struct rb_node *parent; if (RB_EMPTY_NODE(node)) return NULL; /* * If we have a left-hand child, go down and then right as far * as we can. */ if (node->rb_left) { node = node->rb_left; while (node->rb_right) node=node->rb_right; return (struct rb_node *)node; } /* * No left-hand children. Go up till we find an ancestor which * is a right-hand child of its parent. */ while ((parent = rb_parent(node)) && node == parent->rb_left) node = parent; return parent; } void rb_replace_node(struct rb_node *victim, struct rb_node *new, struct rb_root *root) { struct rb_node *parent = rb_parent(victim); /* Set the surrounding nodes to point to the replacement */ __rb_change_child(victim, new, parent, root); if (victim->rb_left) rb_set_parent(victim->rb_left, new); if (victim->rb_right) rb_set_parent(victim->rb_right, new); /* Copy the pointers/colour from the victim to the replacement */ *new = *victim; } static struct rb_node *rb_left_deepest_node(const struct rb_node *node) { for (;;) { if (node->rb_left) node = node->rb_left; else if (node->rb_right) node = node->rb_right; else return (struct rb_node *)node; } } struct rb_node *rb_next_postorder(const struct rb_node *node) { const struct rb_node *parent; if (!node) return NULL; parent = rb_parent(node); /* If we're sitting on node, we've already seen our children */ if (parent && node == parent->rb_left && parent->rb_right) { /* If we are the parent's left node, go to the parent's right * node then all the way down to the left */ return rb_left_deepest_node(parent->rb_right); } else /* Otherwise we are the parent's right node, and the parent * should be next */ return (struct rb_node *)parent; } struct rb_node *rb_first_postorder(const struct rb_root *root) { if (!root->rb_node) return NULL; return rb_left_deepest_node(root->rb_node); } ================================================ FILE: src/lib/stringutil.c ================================================ /************************************************************************* * * 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 . */ ================================================ FILE: src/lib/timer_wheel.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/lib/bitops.h" #include #include #include #include #include #include #include #include "smartdns/lib/timer_wheel.h" #define TVR_BITS 10 #define TVN_BITS 6 #define TVR_SIZE (1 << TVR_BITS) #define TVN_SIZE (1 << TVN_BITS) #define TVR_MASK (TVR_SIZE - 1) #define TVN_MASK (TVN_SIZE - 1) #define INDEX(N) ((base->jiffies >> (TVR_BITS + N * TVN_BITS)) & TVN_MASK) #define MAX_TVAL ((unsigned long)((1ULL << (TVR_BITS + 4 * TVN_BITS)) - 1)) struct tvec { struct list_head vec[TVN_SIZE]; }; struct tvec_root { struct list_head vec[TVR_SIZE]; }; struct tw_base { pthread_spinlock_t lock; pthread_t runner; unsigned long jiffies; struct tvec_root tv1; struct tvec tv2; struct tvec tv3; struct tvec tv4; struct tvec tv5; }; static inline void _tw_add_timer(struct tw_base *base, struct tw_timer_list *timer) { int i; unsigned long idx; unsigned long expires; struct list_head *vec; expires = timer->expires; idx = expires - base->jiffies; if (idx < TVR_SIZE) { i = expires & TVR_MASK; vec = base->tv1.vec + i; } else if (idx < 1 << (TVR_BITS + TVN_BITS)) { i = (expires >> TVR_BITS) & TVN_MASK; vec = base->tv2.vec + i; } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) { i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK; vec = base->tv3.vec + i; } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) { i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK; vec = base->tv4.vec + i; } else if ((signed long)idx < 0) { vec = base->tv1.vec + (base->jiffies & TVR_MASK); } else { if (idx > MAX_TVAL) { idx = MAX_TVAL; expires = idx + base->jiffies; } i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK; vec = base->tv5.vec + i; } list_add_tail(&timer->entry, vec); } static inline void _tw_detach_timer(struct tw_timer_list *timer) { struct list_head *entry = &timer->entry; list_del(entry); entry->next = NULL; } static inline int _tw_cascade(struct tw_base *base, struct tvec *tv, int index) { struct tw_timer_list *timer, *tmp; struct list_head tv_list; list_replace_init(tv->vec + index, &tv_list); list_for_each_entry_safe(timer, tmp, &tv_list, entry) { _tw_add_timer(base, timer); } return index; } static inline int timer_pending(struct tw_timer_list *timer) { struct list_head *entry = &timer->entry; return (entry->next != NULL); } static inline int __detach_if_pending(struct tw_timer_list *timer) { if (!timer_pending(timer)) { return 0; } _tw_detach_timer(timer); return 1; } static inline int __mod_timer(struct tw_base *base, struct tw_timer_list *timer, int pending_only) { int ret = 0; ret = __detach_if_pending(timer); if (!ret && pending_only) { goto done; } ret = 1; _tw_add_timer(base, timer); done: return ret; } void tw_add_timer(struct tw_base *base, struct tw_timer_list *timer) { if (timer->function == NULL) { return; } pthread_spin_lock(&base->lock); { timer->expires += base->jiffies - 1; _tw_add_timer(base, timer); } pthread_spin_unlock(&base->lock); } int tw_del_timer(struct tw_base *base, struct tw_timer_list *timer) { int ret = 0; pthread_spin_lock(&base->lock); { if (timer_pending(timer)) { ret = 1; _tw_detach_timer(timer); } } pthread_spin_unlock(&base->lock); return ret; } int tw_mod_timer_pending(struct tw_base *base, struct tw_timer_list *timer, unsigned long expires) { int ret = 1; pthread_spin_lock(&base->lock); { timer->expires = expires + base->jiffies - 1; ret = __mod_timer(base, timer, 1); } pthread_spin_unlock(&base->lock); return ret; } int tw_mod_timer(struct tw_base *base, struct tw_timer_list *timer, unsigned long expires) { int ret = 1; pthread_spin_lock(&base->lock); { if (timer_pending(timer) && timer->expires == expires) { goto unblock; } timer->expires = expires + base->jiffies - 1; ret = __mod_timer(base, timer, 0); } unblock: pthread_spin_unlock(&base->lock); return ret; } int tw_cleanup_timers(struct tw_base *base) { int ret = 0; void *res = NULL; ret = pthread_cancel(base->runner); if (ret != 0) { goto errout; } ret = pthread_join(base->runner, &res); if (ret != 0) { goto errout; } if (res != PTHREAD_CANCELED) { goto errout; } ret = pthread_spin_destroy(&base->lock); if (ret != 0) { goto errout; } free(base); return 0; errout: return -1; } static inline void run_timers(struct tw_base *base) { unsigned long index, call_time; struct tw_timer_list *timer; struct list_head work_list; struct list_head *head = &work_list; pthread_spin_lock(&base->lock); { index = base->jiffies & TVR_MASK; if (!index && (!_tw_cascade(base, &base->tv2, INDEX(0))) && (!_tw_cascade(base, &base->tv3, INDEX(1))) && (!_tw_cascade(base, &base->tv4, INDEX(2)))) _tw_cascade(base, &base->tv5, INDEX(3)); call_time = base->jiffies++; list_replace_init(base->tv1.vec + index, head); while (!list_empty(head)) { tw_func fn; void *data; timer = list_first_entry(head, struct tw_timer_list, entry); fn = timer->function; data = timer->data; _tw_detach_timer(timer); pthread_spin_unlock(&base->lock); { fn(base, timer, data, call_time); } pthread_spin_lock(&base->lock); } } pthread_spin_unlock(&base->lock); } static unsigned long _tw_tick_count(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000); } static void *timer_work(void *arg) { struct tw_base *base = arg; int sleep = 1000; int sleep_time = 0; unsigned long now = {0}; unsigned long expect_time = 0; unsigned long start_time = 0; now = _tw_tick_count(); start_time = now; expect_time = now + sleep; while (1) { run_timers(base); now = _tw_tick_count(); if (now >= expect_time) { unsigned long elapsed_from_start = now - start_time; unsigned long next_period = (elapsed_from_start / sleep) + 1; expect_time = start_time + next_period * sleep; } sleep_time = (int)(expect_time - now); if (sleep_time < 0) { sleep_time = 0; } usleep(sleep_time * 1000); } return NULL; } struct tw_base *tw_init_timers(void) { int j = 0; int ret = 0; struct timeval tv = { 0, }; struct tw_base *base = NULL; base = calloc(1, sizeof(*base)); if (!base) { goto errout; } ret = pthread_spin_init(&base->lock, 0); if (ret != 0) { goto errout2; } for (j = 0; j < TVN_SIZE; j++) { INIT_LIST_HEAD(base->tv5.vec + j); INIT_LIST_HEAD(base->tv4.vec + j); INIT_LIST_HEAD(base->tv3.vec + j); INIT_LIST_HEAD(base->tv2.vec + j); } for (j = 0; j < TVR_SIZE; j++) { INIT_LIST_HEAD(base->tv1.vec + j); } ret = gettimeofday(&tv, NULL); if (ret < 0) { goto errout1; } base->jiffies = tv.tv_sec; ret = pthread_create(&base->runner, NULL, timer_work, base); if (ret != 0) { goto errout1; } return base; errout1: (void)pthread_spin_destroy(&base->lock); errout2: free(base); errout: return NULL; } ================================================ FILE: src/main.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/smartdns.h" #include #include #include int main(int argc, char *argv[]) { return smartdns_main(argc, argv); } ================================================ FILE: src/proxy.c ================================================ /************************************************************************* * * 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 . */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "smartdns/proxy.h" #include "smartdns/dns_conf.h" #include "smartdns/http_parse.h" #include "smartdns/lib/hashtable.h" #include "smartdns/lib/list.h" #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #include #include #include #include #define PROXY_SOCKS5_VERSION 0x05 #define PROXY_SOCKS5_NO_AUTH 0x00 #define PROXY_SOCKS5_AUTH_USER_PASS 0x02 #define PROXY_SOCKS5_AUTH_NONE 0xFF #define PROXY_SOCKS5_TYPE_IPV4 0x01 #define PROXY_SOCKS5_TYPE_DOMAIN 0x03 #define PROXY_SOCKS5_TYPE_IPV6 0x04 #define PROXY_SOCKS5_CONNECT_TCP 0x01 #define PROXY_SOCKS5_CONNECT_UDP 0x03 #define PROXY_MAX_EVENTS 64 #define PROXY_BUFFER_SIZE (1024 * 4) #define PROXY_MAX_HOSTNAME_LEN 256 #define PROXY_ERROR_LOG_THROTTLE_SEC 60 #define PROXY_THROTTLED_ERROR_LOG(last_time, ...) \ do { \ time_t now = time(NULL); \ if (now - (last_time) >= PROXY_ERROR_LOG_THROTTLE_SEC) { \ tlog(TLOG_ERROR, __VA_ARGS__); \ (last_time) = now; \ } \ } while (0) typedef enum PROXY_CONN_STATE { PROXY_CONN_INIT = 0, PROXY_CONN_INIT_ACK = 1, PROXY_CONN_AUTH = 2, PROXY_CONN_AUTH_ACK = 3, PROXY_CONN_CONNECTING = 4, PROXY_CONN_CONNECTED = 5, } PROXY_CONN_STATE; struct proxy_conn_buffer { int len; uint8_t buffer[PROXY_BUFFER_SIZE]; }; struct proxy_conn { proxy_type_t type; PROXY_CONN_STATE state; char host[DNS_MAX_CNAME_LEN]; unsigned short port; int fd; int udp_fd; int buffer_len; int is_udp; int non_block; struct sockaddr_storage udp_dest_addr; socklen_t udp_dest_addrlen; struct proxy_conn_buffer buffer; struct proxy_server_info *server_info; }; /* upstream server groups */ struct proxy_server_info { struct hlist_node node; char proxy_name[PROXY_NAME_LEN]; struct sockaddr_storage server_addr; socklen_t server_addrlen; struct proxy_info info; }; struct proxy_struct { int run; int epoll_fd; pthread_t tid; pthread_mutex_t proxy_lock; DECLARE_HASHTABLE(proxy_server, 4); }; static struct proxy_struct proxy; static int is_proxy_init; static const char *proxy_socks5_status_code[] = { "success", "general SOCKS server failure", "connection not allowed by ruleset", "Network unreachable", "Host unreachable", "Connection refused", "TTL expired", "Command not supported", "Address type not supported", }; /* get server group by name */ static struct proxy_server_info *_proxy_get_server_info(const char *proxy_name) { unsigned long key; struct proxy_server_info *server_info = NULL; struct hlist_node *tmp = NULL; if (proxy_name == NULL) { return NULL; } key = hash_string(proxy_name); hash_for_each_possible_safe(proxy.proxy_server, server_info, tmp, node, key) { if (strncmp(server_info->proxy_name, proxy_name, DNS_GROUP_NAME_LEN) != 0) { continue; } return server_info; } return NULL; } static struct addrinfo *_proxy_getaddr(const char *host, int port, int type, int protocol) { struct addrinfo hints; struct addrinfo *result = NULL; int ret = 0; char port_str[32]; snprintf(port_str, sizeof(port_str), "%d", port); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = type; hints.ai_protocol = protocol; ret = getaddrinfo(host, port_str, &hints, &result); if (ret != 0) { tlog(TLOG_ERROR, "get addr info failed. %s\n", gai_strerror(ret)); tlog(TLOG_ERROR, "host: %s, port: %d, type: %d, protocol: %d", host, port, type, protocol); goto errout; } return result; errout: if (result) { freeaddrinfo(result); } return NULL; } int proxy_add(const char *proxy_name, struct proxy_info *info) { unsigned long key; char ip_str[PROXY_MAX_IPLEN]; int port = 0; struct addrinfo *gai = NULL; struct proxy_server_info *server_info = _proxy_get_server_info(proxy_name); if (server_info) { return -1; } server_info = zalloc(1, sizeof(*server_info)); if (server_info == NULL) { goto errout; } memcpy(&server_info->info, info, sizeof(struct proxy_info)); if (parse_ip(info->server, ip_str, &port) != 0) { goto errout; } port = info->port; gai = _proxy_getaddr(info->server, port, SOCK_STREAM, 0); if (gai == NULL) { goto errout; } server_info->server_addrlen = gai->ai_addrlen; memcpy(&server_info->server_addr, gai->ai_addr, gai->ai_addrlen); safe_strncpy(server_info->proxy_name, proxy_name, PROXY_NAME_LEN); key = hash_string(server_info->proxy_name); hash_add(proxy.proxy_server, &server_info->node, key); freeaddrinfo(gai); return 0; errout: if (server_info) { free(server_info); server_info = NULL; } if (gai) { freeaddrinfo(gai); } return -1; } static int _proxy_remove(struct proxy_server_info *server_info) { hash_del(&server_info->node); free(server_info); return 0; } int proxy_remove(const char *proxy_name) { struct proxy_server_info *server_info = _proxy_get_server_info(proxy_name); if (server_info == NULL) { return 0; } _proxy_remove(server_info); return 0; } static void _proxy_remove_all(void) { struct proxy_server_info *server_info = NULL; struct hlist_node *tmp = NULL; unsigned int i = 0; hash_for_each_safe(proxy.proxy_server, i, tmp, server_info, node) { _proxy_remove(server_info); } } struct proxy_conn *proxy_conn_new(const char *proxy_name, const char *host, int port, int is_udp, int non_block) { struct proxy_conn *proxy_conn = NULL; struct proxy_server_info *server_info = NULL; struct addrinfo *gai = NULL; int fd = -1; server_info = _proxy_get_server_info(proxy_name); if (server_info == NULL) { tlog(TLOG_WARN, "proxy server %s not found", proxy_name); goto errout; } if (is_udp == 1 && server_info->info.type != PROXY_SOCKS5) { tlog(TLOG_WARN, "only socks5 support udp"); goto errout; } fd = socket(server_info->server_addr.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { goto errout; } proxy_conn = zalloc(1, sizeof(*proxy_conn)); if (proxy_conn == NULL) { goto errout; } safe_strncpy(proxy_conn->host, host, DNS_MAX_CNAME_LEN); proxy_conn->port = port; proxy_conn->type = server_info->info.type; proxy_conn->state = PROXY_CONN_INIT; proxy_conn->server_info = server_info; proxy_conn->fd = fd; proxy_conn->udp_fd = -1; proxy_conn->is_udp = is_udp; proxy_conn->non_block = non_block; if (non_block) { set_fd_nonblock(fd, 1); } return proxy_conn; errout: if (proxy_conn) { free(proxy_conn); proxy_conn = NULL; } if (fd >= 0) { close(fd); } if (gai) { freeaddrinfo(gai); } return NULL; } void proxy_conn_free(struct proxy_conn *proxy_conn) { if (proxy_conn == NULL) { return; } if (proxy_conn->fd >= 0) { close(proxy_conn->fd); } if (proxy_conn->udp_fd >= 0) { close(proxy_conn->udp_fd); } free(proxy_conn); } int proxy_conn_connect(struct proxy_conn *proxy_conn) { if (proxy_conn == NULL) { return -1; } return connect(proxy_conn->fd, (struct sockaddr *)&proxy_conn->server_info->server_addr, proxy_conn->server_info->server_addrlen); } static int _proxy_handshake_socks5_create_udp_fd(struct proxy_conn *proxy_conn) { int ret = 0; char *gai_host = NULL; int udp_fd = -1; struct addrinfo *gai = NULL; switch (proxy_conn->udp_dest_addr.ss_family) { case AF_INET: gai_host = "0.0.0.0"; break; case AF_INET6: gai_host = "::"; break; default: goto errout; break; } gai = _proxy_getaddr(gai_host, 0, SOCK_DGRAM, 0); udp_fd = socket(gai->ai_family, gai->ai_socktype | SOCK_CLOEXEC, 0); if (udp_fd < 0) { goto errout; } ret = bind(udp_fd, gai->ai_addr, gai->ai_addrlen); if (ret < 0) { goto errout; } if (proxy_conn->non_block) { set_fd_nonblock(udp_fd, 1); } freeaddrinfo(gai); return udp_fd; errout: if (udp_fd >= 0) { close(udp_fd); } if (gai) { freeaddrinfo(gai); } return -1; } static int _proxy_handshake_socks5_connect_udp(struct proxy_conn *proxy_conn) { int udp_fd = -1; if (proxy_conn->is_udp == 0) { return 0; } if (proxy_conn->udp_fd < 0) { udp_fd = _proxy_handshake_socks5_create_udp_fd(proxy_conn); if (udp_fd < 0) { return -1; } proxy_conn->udp_fd = udp_fd; } return connect(proxy_conn->udp_fd, (struct sockaddr *)&proxy_conn->udp_dest_addr, proxy_conn->udp_dest_addrlen); } static proxy_handshake_state _proxy_handshake_socks5_reply_connect_addr(struct proxy_conn *proxy_conn) { char buff[DNS_MAX_CNAME_LEN * 2]; int len = 0; memset(buff, 0, sizeof(buff)); struct sockaddr_storage addr; char *ptr = NULL; socklen_t addr_len = sizeof(addr); buff[0] = PROXY_SOCKS5_VERSION; if (proxy_conn->is_udp) { buff[1] = PROXY_SOCKS5_CONNECT_UDP; } else { buff[1] = PROXY_SOCKS5_CONNECT_TCP; } buff[2] = 0x0; ptr = buff + 3; if (proxy_conn->server_info->info.use_domain) { *ptr = PROXY_SOCKS5_TYPE_DOMAIN; ptr++; int domainlen = strnlen(proxy_conn->host, DNS_MAX_CNAME_LEN); *ptr = domainlen; ptr++; memcpy(ptr, proxy_conn->host, domainlen); ptr += domainlen; } else { if (proxy_conn->is_udp) { memset(&addr, 0, proxy_conn->server_info->server_addrlen); addr_len = proxy_conn->server_info->server_addrlen; addr.ss_family = proxy_conn->server_info->server_addr.ss_family; } else { getaddr_by_host(proxy_conn->host, (struct sockaddr *)&addr, &addr_len); } switch (addr.ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&addr; *ptr = PROXY_SOCKS5_TYPE_IPV4; ptr++; memcpy(ptr, &addr_in->sin_addr.s_addr, 4); ptr += 4; } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { *ptr = PROXY_SOCKS5_TYPE_IPV4; ptr++; memcpy(ptr, addr_in6->sin6_addr.s6_addr + 12, 4); ptr += 4; } else { *ptr = PROXY_SOCKS5_TYPE_IPV6; ptr++; memcpy(ptr, addr_in6->sin6_addr.s6_addr, 16); ptr += 16; } } break; default: return PROXY_HANDSHAKE_ERR; } } *((short *)(ptr)) = htons(proxy_conn->port); ptr += 2; len = send(proxy_conn->fd, buff, ptr - buff, MSG_NOSIGNAL); if (len != ptr - buff) { tlog(TLOG_ERROR, "Send proxy request failed."); return PROXY_HANDSHAKE_ERR; } proxy_conn->state = PROXY_CONN_CONNECTING; return PROXY_HANDSHAKE_WANT_READ; } static proxy_handshake_state _proxy_handshake_socks5_send_auth(struct proxy_conn *proxy_conn) { char buff[DNS_MAX_CNAME_LEN * 2]; int len = 0; int offset = 0; memset(buff, 0, sizeof(buff)); buff[0] = 0x1; buff[1] = strnlen(proxy_conn->server_info->info.username, PROXY_MAX_NAMELEN); safe_strncpy(buff + 2, proxy_conn->server_info->info.username, buff[1] + 1); offset = buff[1] + 2; buff[offset] = strnlen(proxy_conn->server_info->info.password, PROXY_MAX_NAMELEN); safe_strncpy(buff + offset + 1, proxy_conn->server_info->info.password, buff[offset] + 1); offset += buff[offset] + 1; len = send(proxy_conn->fd, buff, offset, MSG_NOSIGNAL); if (len != offset) { tlog(TLOG_ERROR, "send auth failed, len: %d, %s", len, strerror(errno)); return PROXY_HANDSHAKE_ERR; } proxy_conn->state = PROXY_CONN_AUTH_ACK; return PROXY_HANDSHAKE_WANT_READ; } static proxy_handshake_state _proxy_handshake_socks5(struct proxy_conn *proxy_conn) { int len = 0; char buff[DNS_MAX_CNAME_LEN * 2]; static time_t last_error_log_time = 0; memset(buff, 0, sizeof(buff)); if (proxy_conn == NULL) { return PROXY_HANDSHAKE_ERR; } if (proxy_conn->fd < 0) { return PROXY_HANDSHAKE_ERR; } switch (proxy_conn->state) { case PROXY_CONN_INIT: { buff[0] = PROXY_SOCKS5_VERSION; buff[1] = 0x2; // 2 auth methods buff[2] = PROXY_SOCKS5_NO_AUTH; buff[3] = PROXY_SOCKS5_AUTH_USER_PASS; len = send(proxy_conn->fd, buff, 4, MSG_NOSIGNAL); if (len != 4) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "connect socks5 server %s failed, %s", proxy_conn->server_info->proxy_name, strerror(errno)); return PROXY_HANDSHAKE_ERR; } proxy_conn->state = PROXY_CONN_INIT_ACK; return PROXY_HANDSHAKE_WANT_READ; } break; case PROXY_CONN_INIT_ACK: len = recv(proxy_conn->fd, proxy_conn->buffer.buffer + proxy_conn->buffer.len, sizeof(proxy_conn->buffer.buffer), 0); if (len <= 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return PROXY_HANDSHAKE_WANT_READ; } PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "recv socks5 init ack from %s failed, %s", proxy_conn->server_info->proxy_name, strerror(errno)); return PROXY_HANDSHAKE_ERR; } proxy_conn->buffer.len += len; if (proxy_conn->buffer.len < 2) { return PROXY_HANDSHAKE_WANT_READ; } if (proxy_conn->buffer.len > 2) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "recv socks5 init ack from %s failed", proxy_conn->server_info->proxy_name); return PROXY_HANDSHAKE_ERR; } proxy_conn->buffer.len = 0; if (proxy_conn->buffer.buffer[0] != PROXY_SOCKS5_VERSION) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "server %s not support socks5", proxy_conn->server_info->proxy_name); return PROXY_HANDSHAKE_ERR; } if ((unsigned char)proxy_conn->buffer.buffer[1] == PROXY_SOCKS5_AUTH_NONE) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "server %s not support auth methods", proxy_conn->server_info->proxy_name); return PROXY_HANDSHAKE_ERR; } tlog(TLOG_DEBUG, "server %s select auth method is %d", proxy_conn->server_info->proxy_name, proxy_conn->buffer.buffer[1]); if (proxy_conn->buffer.buffer[1] == PROXY_SOCKS5_AUTH_USER_PASS) { return _proxy_handshake_socks5_send_auth(proxy_conn); } if (proxy_conn->buffer.buffer[1] == PROXY_SOCKS5_NO_AUTH) { return _proxy_handshake_socks5_reply_connect_addr(proxy_conn); } PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "server %s select invalid auth method %d", proxy_conn->server_info->proxy_name, proxy_conn->buffer.buffer[1]); return PROXY_HANDSHAKE_ERR; break; case PROXY_CONN_AUTH_ACK: len = recv(proxy_conn->fd, proxy_conn->buffer.buffer + proxy_conn->buffer.len, sizeof(proxy_conn->buffer.buffer), 0); if (len <= 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return PROXY_HANDSHAKE_WANT_READ; } PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "recv socks5 auth ack from %s failed, %s", proxy_conn->server_info->proxy_name, strerror(errno)); return PROXY_HANDSHAKE_ERR; } proxy_conn->buffer.len += len; if (proxy_conn->buffer.len < 2) { return PROXY_HANDSHAKE_WANT_READ; } if (proxy_conn->buffer.len != 2) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "recv socks5 auth ack from %s failed", proxy_conn->server_info->proxy_name); return PROXY_HANDSHAKE_ERR; } proxy_conn->buffer.len = 0; if (proxy_conn->buffer.buffer[0] != 0x1) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "server %s not support socks5", proxy_conn->server_info->proxy_name); return PROXY_HANDSHAKE_ERR; } if (proxy_conn->buffer.buffer[1] != 0x0) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "server %s auth failed, incorrect user or password, code: %d", proxy_conn->server_info->proxy_name, proxy_conn->buffer.buffer[1]); return PROXY_HANDSHAKE_ERR; } tlog(TLOG_DEBUG, "server %s auth success", proxy_conn->server_info->proxy_name); proxy_conn->state = PROXY_CONN_CONNECTING; return _proxy_handshake_socks5_reply_connect_addr(proxy_conn); case PROXY_CONN_CONNECTING: { unsigned char addr[16]; unsigned short port = 0; int use_dest_ip = 0; unsigned char *recv_buff = NULL; int addr_len = 0; len = recv(proxy_conn->fd, proxy_conn->buffer.buffer + proxy_conn->buffer.len, sizeof(proxy_conn->buffer.buffer), 0); if (len <= 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return PROXY_HANDSHAKE_WANT_READ; } if (len == 0) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "server %s closed connection", proxy_conn->server_info->proxy_name); } else { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "recv socks5 connect ack from %s failed, %s", proxy_conn->server_info->proxy_name, strerror(errno)); } return PROXY_HANDSHAKE_ERR; } proxy_conn->buffer.len += len; if (proxy_conn->buffer.len < 10) { return PROXY_HANDSHAKE_WANT_READ; } recv_buff = proxy_conn->buffer.buffer; if (recv_buff[0] != PROXY_SOCKS5_VERSION) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "server %s not support socks5", proxy_conn->server_info->proxy_name); return PROXY_HANDSHAKE_ERR; } if (recv_buff[1] != 0) { if (recv_buff[1] <= (sizeof(proxy_socks5_status_code) / sizeof(proxy_socks5_status_code[0]))) { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "server %s reply failed, error-code: %s", proxy_conn->server_info->proxy_name, proxy_socks5_status_code[(int)recv_buff[1]]); } else { PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "server %s reply failed, error-code: %x", proxy_conn->server_info->proxy_name, recv_buff[1]); } return PROXY_HANDSHAKE_ERR; } switch (recv_buff[3]) { case PROXY_SOCKS5_TYPE_IPV4: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&proxy_conn->udp_dest_addr; proxy_conn->udp_dest_addrlen = sizeof(struct sockaddr_in); if (proxy_conn->buffer.len < 10) { return PROXY_HANDSHAKE_WANT_READ; } addr_len = 4; memcpy(addr, recv_buff + 4, addr_len); port = ntohs(*((short *)(recv_buff + 4 + addr_len))); addr_in->sin_family = AF_INET; addr_in->sin_addr.s_addr = *((int *)addr); addr_in->sin_port = *((short *)(recv_buff + 4 + addr_len)); if (addr[0] == 0 && addr[1] == 0 && addr[2] == 0 && addr[3] == 0) { use_dest_ip = 1; } tlog(TLOG_DEBUG, "server %s proxy dest: %d.%d.%d.%d:%d\n", proxy_conn->server_info->proxy_name, addr[0], addr[1], addr[2], addr[3], port); } break; case PROXY_SOCKS5_TYPE_IPV6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&proxy_conn->udp_dest_addr; proxy_conn->udp_dest_addrlen = sizeof(struct sockaddr_in6); if (proxy_conn->buffer.len < 22) { return PROXY_HANDSHAKE_WANT_READ; } addr_len = 16; memcpy(addr, recv_buff + 4, addr_len); port = ntohs(*((short *)(recv_buff + 4 + addr_len))); addr_in6->sin6_family = AF_INET6; memcpy(addr_in6->sin6_addr.s6_addr, addr, addr_len); addr_in6->sin6_port = *((short *)(recv_buff + 4 + addr_len)); if (addr[0] == 0 && addr[1] == 0 && addr[2] == 0 && addr[3] == 0 && addr[4] == 0 && addr[5] == 0 && addr[6] == 0 && addr[7] == 0 && addr[8] == 0 && addr[9] == 0 && addr[10] == 0 && addr[11] == 0 && addr[12] == 0 && addr[13] == 0 && addr[14] == 0 && addr[15] == 0) { use_dest_ip = 1; } tlog(TLOG_DEBUG, "server %s proxy dest: [%x:%x:%x:%x:%x:%x:%x:%x]:%d\n", proxy_conn->server_info->proxy_name, ntohs(*((short *)addr)), ntohs(*((short *)(addr + 2))), ntohs(*((short *)(addr + 4))), ntohs(*((short *)(addr + 6))), ntohs(*((short *)(addr + 8))), ntohs(*((short *)(addr + 10))), ntohs(*((short *)(addr + 12))), ntohs(*((short *)(addr + 14))), port); } break; default: return PROXY_HANDSHAKE_ERR; } if (use_dest_ip && proxy_conn->is_udp) { memcpy(&proxy_conn->udp_dest_addr, &proxy_conn->server_info->server_addr, proxy_conn->server_info->server_addrlen); proxy_conn->udp_dest_addrlen = proxy_conn->server_info->server_addrlen; switch (proxy_conn->udp_dest_addr.ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&proxy_conn->udp_dest_addr; addr_in->sin_port = *((short *)(recv_buff + 4 + addr_len)); } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&proxy_conn->udp_dest_addr; addr_in6->sin6_port = *((short *)(recv_buff + 4 + addr_len)); } break; default: return PROXY_HANDSHAKE_ERR; break; } } if (_proxy_handshake_socks5_connect_udp(proxy_conn) != 0) { return PROXY_HANDSHAKE_ERR; } proxy_conn->state = PROXY_CONN_CONNECTED; tlog(TLOG_DEBUG, "success connect to socks5 proxy server %s, type: %s", proxy_conn->server_info->proxy_name, proxy_conn->is_udp ? "udp" : "tcp"); return PROXY_HANDSHAKE_CONNECTED; } break; default: PROXY_THROTTLED_ERROR_LOG(last_error_log_time, "client socks5 status %d is invalid", proxy_conn->state); return PROXY_HANDSHAKE_ERR; } return PROXY_HANDSHAKE_ERR; } static int _proxy_handshake_http(struct proxy_conn *proxy_conn) { int len = 0; proxy_handshake_state ret = PROXY_HANDSHAKE_ERR; char buff[4096]; struct http_head *http_head = NULL; if (proxy_conn == NULL) { return PROXY_HANDSHAKE_ERR; } if (proxy_conn->fd < 0) { return PROXY_HANDSHAKE_ERR; } switch (proxy_conn->state) { case PROXY_CONN_INIT: { char connecthost[DNS_MAX_CNAME_LEN * 2]; struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); getaddr_by_host(proxy_conn->host, (struct sockaddr *)&addr, &addr_len); if (proxy_conn->server_info->info.use_domain) { snprintf(connecthost, sizeof(connecthost), "%s:%d", proxy_conn->host, proxy_conn->port); } else { struct sockaddr_in *addr_in; addr_in = (struct sockaddr_in *)&addr; unsigned char *paddr = (unsigned char *)&addr_in->sin_addr.s_addr; snprintf(connecthost, sizeof(connecthost), "%d.%d.%d.%d:%d", paddr[0], paddr[1], paddr[2], paddr[3], proxy_conn->port); } int msglen = 0; if (proxy_conn->server_info->info.username[0] == '\0') { msglen = snprintf(buff, sizeof(buff), "CONNECT %s HTTP/1.1\r\n" "Host: %s\r\n" "Proxy-Connection: Keep-Alive\r\n\r\n", connecthost, connecthost); } else { char auth[256]; char base64_auth[256 * 2]; snprintf(auth, sizeof(auth), "%s:%s", proxy_conn->server_info->info.username, proxy_conn->server_info->info.password); SSL_base64_encode(auth, strlen(auth), base64_auth); msglen = snprintf(buff, sizeof(buff), "CONNECT %s HTTP/1.1\r\n" "Host: %s\r\n" "Proxy-Authorization: Basic %s\r\n" "Proxy-Connection: Keep-Alive\r\n\r\n", connecthost, connecthost, base64_auth); } len = send(proxy_conn->fd, buff, msglen, MSG_NOSIGNAL); if (len != msglen) { tlog(TLOG_ERROR, "connect to https proxy server %s failed, %s", proxy_conn->server_info->proxy_name, strerror(errno)); goto out; } proxy_conn->state = PROXY_CONN_CONNECTING; ret = PROXY_HANDSHAKE_WANT_READ; goto out; } break; case PROXY_CONN_CONNECTING: { http_head = http_head_init(4096, HTTP_VERSION_1_1); if (http_head == NULL) { goto out; } len = recv(proxy_conn->fd, proxy_conn->buffer.buffer + proxy_conn->buffer.len, sizeof(proxy_conn->buffer.buffer), 0); if (len <= 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { ret = PROXY_HANDSHAKE_WANT_READ; goto out; } if (len == 0) { tlog(TLOG_ERROR, "remote server %s closed.", proxy_conn->server_info->proxy_name); } else { tlog(TLOG_ERROR, "recv from %s failed, %s", proxy_conn->server_info->proxy_name, strerror(errno)); } goto out; } proxy_conn->buffer.len += len; len = http_head_parse(http_head, proxy_conn->buffer.buffer, proxy_conn->buffer.len); if (len < 0) { if (len == -1) { ret = PROXY_HANDSHAKE_WANT_READ; goto out; } tlog(TLOG_DEBUG, "remote server %s not supported.", proxy_conn->server_info->proxy_name); goto out; } if (http_head_get_httpcode(http_head) != 200) { tlog(TLOG_WARN, "http server %s query failed, server return http code : %d, %s", proxy_conn->server_info->proxy_name, http_head_get_httpcode(http_head), http_head_get_httpcode_msg(http_head)); goto out; } proxy_conn->buffer.len -= len; if (proxy_conn->buffer.len > 0) { memmove(proxy_conn->buffer.buffer, proxy_conn->buffer.buffer + len, proxy_conn->buffer.len); } if (proxy_conn->buffer.len < 0) { proxy_conn->buffer.len = 0; } tlog(TLOG_DEBUG, "success connect to http proxy server %s", proxy_conn->server_info->proxy_name); proxy_conn->state = PROXY_CONN_CONNECTED; ret = PROXY_HANDSHAKE_CONNECTED; goto out; } break; default: goto out; break; } out: if (http_head) { http_head_destroy(http_head); } return ret; } proxy_handshake_state proxy_conn_handshake(struct proxy_conn *proxy_conn) { if (proxy_conn == NULL) { return -1; } if (proxy_conn->state == PROXY_CONN_CONNECTED) { return PROXY_HANDSHAKE_OK; } switch (proxy_conn->type) { case PROXY_SOCKS5: return _proxy_handshake_socks5(proxy_conn); case PROXY_HTTP: return _proxy_handshake_http(proxy_conn); default: return PROXY_HANDSHAKE_ERR; } return PROXY_HANDSHAKE_ERR; } static int _proxy_is_tcp_connected(struct proxy_conn *proxy_conn) { char buff[1]; int ret = 0; if (proxy_conn == NULL) { return 0; } ret = recv(proxy_conn->fd, buff, 1, MSG_PEEK | MSG_DONTWAIT); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return 1; } } return 0; } int proxy_conn_sendto(struct proxy_conn *proxy_conn, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { char buffer[PROXY_BUFFER_SIZE]; int buffer_len = 0; int ret = 0; if (proxy_conn == NULL) { return -1; } if (proxy_conn->udp_fd < 0) { return -1; } if (_proxy_is_tcp_connected(proxy_conn) == 0) { errno = ECONNRESET; return -1; } buffer[0] = 0x00; buffer[1] = 0x00; buffer[2] = 0x00; buffer_len += 3; switch (dest_addr->sa_family) { case AF_INET: buffer[3] = PROXY_SOCKS5_TYPE_IPV4; memcpy(buffer + 4, &((struct sockaddr_in *)dest_addr)->sin_addr.s_addr, 4); memcpy(buffer + 8, &((struct sockaddr_in *)dest_addr)->sin_port, 2); buffer_len += 7; break; case AF_INET6: buffer[3] = PROXY_SOCKS5_TYPE_IPV6; memcpy(buffer + 4, &((struct sockaddr_in6 *)dest_addr)->sin6_addr.s6_addr, 16); memcpy(buffer + 20, &((struct sockaddr_in6 *)dest_addr)->sin6_port, 2); buffer_len += 19; break; default: return -1; } if (sizeof(buffer) - buffer_len <= len) { errno = ENOSPC; return -1; } memcpy(buffer + buffer_len, buf, len); buffer_len += len; ret = sendto(proxy_conn->udp_fd, buffer, buffer_len, MSG_NOSIGNAL, (struct sockaddr *)&proxy_conn->udp_dest_addr, proxy_conn->udp_dest_addrlen); if (ret != buffer_len) { return -1; } return len; } int proxy_conn_recvfrom(struct proxy_conn *proxy_conn, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) { char buffer[PROXY_BUFFER_SIZE]; int buffer_len = 0; int ret = 0; if (proxy_conn == NULL) { return -1; } if (proxy_conn->udp_fd < 0) { return -1; } ret = recvfrom(proxy_conn->udp_fd, buffer, sizeof(buffer), MSG_NOSIGNAL, NULL, NULL); if (ret <= 0) { return -1; } if (buffer[0] != 0x00 || buffer[1] != 0x00 || buffer[2] != 0x00) { return -1; } switch (buffer[3]) { case PROXY_SOCKS5_TYPE_IPV4: if (ret < 10) { return -1; } if (src_addr) { memset(src_addr, 0, sizeof(struct sockaddr_in)); ((struct sockaddr_in *)src_addr)->sin_family = AF_INET; memcpy(&((struct sockaddr_in *)src_addr)->sin_addr.s_addr, buffer + 4, 4); memcpy(&((struct sockaddr_in *)src_addr)->sin_port, buffer + 8, 2); } if (addrlen) { *addrlen = sizeof(struct sockaddr_in); } buffer_len = 10; break; case PROXY_SOCKS5_TYPE_IPV6: if (ret < 22) { return -1; } if (src_addr) { memset(src_addr, 0, sizeof(struct sockaddr_in6)); ((struct sockaddr_in6 *)src_addr)->sin6_family = AF_INET6; memcpy(&((struct sockaddr_in6 *)src_addr)->sin6_addr.s6_addr, buffer + 4, 16); memcpy(&((struct sockaddr_in6 *)src_addr)->sin6_port, buffer + 20, 2); } if (addrlen) { *addrlen = sizeof(struct sockaddr_in6); } buffer_len = 22; break; default: return -1; } if (ret - buffer_len > (int)len) { return -1; } memcpy(buf, buffer + buffer_len, ret - buffer_len); return ret - buffer_len; } int proxy_conn_get_fd(struct proxy_conn *proxy_conn) { if (proxy_conn == NULL) { return -1; } return proxy_conn->fd; } int proxy_conn_get_udpfd(struct proxy_conn *proxy_conn) { if (proxy_conn == NULL) { return -1; } return proxy_conn->udp_fd; } int proxy_conn_is_udp(struct proxy_conn *proxy_conn) { if (proxy_conn == NULL) { return -1; } return proxy_conn->is_udp; } int proxy_init(void) { if (is_proxy_init == 1) { return -1; } memset(&proxy, 0, sizeof(proxy)); hash_init(proxy.proxy_server); is_proxy_init = 1; return 0; } void proxy_exit(void) { if (is_proxy_init == 0) { return; } _proxy_remove_all(); is_proxy_init = 0; return; } ================================================ FILE: src/smartdns.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/smartdns.h" #include "smartdns/lib/art.h" #include "smartdns/lib/atomic.h" #include "smartdns/lib/hashtable.h" #include "smartdns/lib/list.h" #include "smartdns/lib/rbtree.h" #include "smartdns/timer.h" #include "smartdns/tlog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_KEY_LEN 64 #define SMARTDNS_PID_FILE "/run/smartdns.pid" #define SMARTDNS_LEGACY_PID_FILE "/var/run/smartdns.pid" #define TMP_BUFF_LEN_32 32 #define SMARTDNS_CRASH_CODE 254 typedef enum { SMARTDNS_RUN_MONITOR_OK = 0, SMARTDNS_RUN_MONITOR_ERROR = 1, SMARTDNS_RUN_MONITOR_EXIT = 2, } smartdns_run_monitor_ret; static int verbose_screen; static int exit_status; static int exit_restart; static void _help(void) { /* clang-format off */ char *help = "" "Usage: smartdns [OPTION]...\n" "Start smartdns server.\n" " -f run foreground.\n" " -c [conf] config file.\n" " -p [pid] pid file path, '-' means don't create pid file.\n" " -R restart smartdns when crash.\n" " -S ignore segment fault signal.\n" " -x verbose screen.\n" " -v display version.\n" " -h show this help message.\n" "" "Debug options:\n" #ifdef DEBUG " -N [file] dump dns packet to file.\n" #endif " --cache-print [file] print cache.\n" " --is-quic-supported is quic http3 supported.\n" "" "Online help: https://pymumu.github.io/smartdns\n" "Copyright (C) Nick Peng \n" ; /* clang-format on */ printf("%s", help); } static void _smartdns_get_version(char *str_ver, int str_ver_len) { char commit_ver[TMP_BUFF_LEN_32 * 2] = {0}; #ifdef COMMIT_VERION snprintf(commit_ver, sizeof(commit_ver), " (%s)", COMMIT_VERION); #endif #ifdef SMARTDNS_VERION const char *ver = SMARTDNS_VERION; snprintf(str_ver, str_ver_len, "%s%s", ver, commit_ver); #else struct tm tm; get_compiled_time(&tm); snprintf(str_ver, str_ver_len, "1.%.4d%.2d%.2d-%.2d%.2d%s", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, commit_ver); #endif } const char *smartdns_version(void) { static char str_ver[256] = {0}; if (str_ver[0] == 0) { _smartdns_get_version(str_ver, sizeof(str_ver)); } return str_ver; } static void _show_version(void) { char str_ver[256] = {0}; _smartdns_get_version(str_ver, sizeof(str_ver)); printf("smartdns %s\n", str_ver); } static int _smartdns_load_from_resolv_file(const char *resolv_file) { FILE *fp = NULL; char line[MAX_LINE_LEN]; char key[MAX_KEY_LEN] = {0}; char value[MAX_LINE_LEN]; char ns_ip[DNS_MAX_IPLEN]; int port = PORT_NOT_DEFINED; int ret = -1; int filed_num = 0; fp = fopen(resolv_file, "r"); if (fp == NULL) { tlog(TLOG_ERROR, "open %s failed, %s", resolv_file, strerror(errno)); return -1; } while (fgets(line, MAX_LINE_LEN, fp)) { filed_num = sscanf(line, "%63s %1023[^\r\n]s", key, value); if (filed_num != 2) { continue; } if (strncmp(key, "nameserver", MAX_KEY_LEN - 1) != 0) { continue; } if (parse_ip(value, ns_ip, &port) != 0) { continue; } if (port == PORT_NOT_DEFINED) { port = DEFAULT_DNS_PORT; } safe_strncpy(dns_conf.servers[dns_conf.server_num].server, ns_ip, DNS_MAX_IPLEN); dns_conf.servers[dns_conf.server_num].port = port; dns_conf.servers[dns_conf.server_num].type = DNS_SERVER_UDP; dns_conf.servers[dns_conf.server_num].set_mark = -1; dns_conf.server_num++; ret = 0; } fclose(fp); return ret; } static int _smartdns_load_from_resolv(void) { return _smartdns_load_from_resolv_file(dns_conf.dns_resolv_file); } static int _smartdns_load_from_default_resolv(void) { return _smartdns_load_from_resolv_file(DNS_RESOLV_FILE); } static int _smartdns_prepare_server_flags(struct client_dns_server_flags *flags, struct dns_servers *server) { memset(flags, 0, sizeof(*flags)); switch (server->type) { case DNS_SERVER_UDP: { struct client_dns_server_flag_udp *flag_udp = &flags->udp; flag_udp->ttl = server->ttl; } break; case DNS_SERVER_HTTP3: case DNS_SERVER_HTTPS: { struct client_dns_server_flag_https *flag_http = &flags->https; if (server->spki[0] != 0) { flag_http->spi_len = dns_client_spki_decode(server->spki, (unsigned char *)flag_http->spki, sizeof(flag_http->spki)); if (flag_http->spi_len <= 0) { tlog(TLOG_ERROR, "decode spki failed, %s:%d", server->server, server->port); return -1; } } safe_strncpy(flag_http->hostname, server->hostname, sizeof(flag_http->hostname)); safe_strncpy(flag_http->path, server->path, sizeof(flag_http->path)); safe_strncpy(flag_http->httphost, server->httphost, sizeof(flag_http->httphost)); safe_strncpy(flag_http->tls_host_verify, server->tls_host_verify, sizeof(flag_http->tls_host_verify)); safe_strncpy(flag_http->alpn, server->alpn, DNS_MAX_ALPN_LEN); flag_http->skip_check_cert = server->skip_check_cert; } break; case DNS_SERVER_QUIC: case DNS_SERVER_TLS: { struct client_dns_server_flag_tls *flag_tls = &flags->tls; if (server->spki[0] != 0) { flag_tls->spi_len = dns_client_spki_decode(server->spki, (unsigned char *)flag_tls->spki, sizeof(flag_tls->spki)); if (flag_tls->spi_len <= 0) { tlog(TLOG_ERROR, "decode spki failed, %s:%d", server->server, server->port); return -1; } } safe_strncpy(flag_tls->hostname, server->hostname, sizeof(flag_tls->hostname)); safe_strncpy(flag_tls->tls_host_verify, server->tls_host_verify, sizeof(flag_tls->tls_host_verify)); safe_strncpy(flag_tls->alpn, server->alpn, DNS_MAX_ALPN_LEN); flag_tls->skip_check_cert = server->skip_check_cert; } break; case DNS_SERVER_TCP: break; default: return -1; break; } flags->type = server->type; flags->server_flag = server->server_flag; flags->result_flag = server->result_flag; flags->set_mark = server->set_mark; flags->drop_packet_latency_ms = server->drop_packet_latency_ms; flags->tcp_keepalive = server->tcp_keepalive; flags->subnet_all_query_types = server->subnet_all_query_types; flags->fallback = server->fallback; safe_strncpy(flags->proxyname, server->proxyname, sizeof(flags->proxyname)); safe_strncpy(flags->ifname, server->ifname, sizeof(flags->ifname)); if (server->ipv4_ecs.enable) { flags->ipv4_ecs.enable = 1; safe_strncpy(flags->ipv4_ecs.ip, server->ipv4_ecs.ip, sizeof(flags->ipv4_ecs.ip)); flags->ipv4_ecs.subnet = server->ipv4_ecs.subnet; } if (server->ipv6_ecs.enable) { flags->ipv6_ecs.enable = 1; safe_strncpy(flags->ipv6_ecs.ip, server->ipv6_ecs.ip, sizeof(flags->ipv6_ecs.ip)); flags->ipv6_ecs.subnet = server->ipv6_ecs.subnet; } return 0; } static int _smartdns_add_servers(void) { unsigned long i = 0; int j = 0; int ret = 0; struct dns_server_groups *group = NULL; struct dns_servers *server = NULL; struct client_dns_server_flags flags; for (i = 0; i < (unsigned int)dns_conf.server_num; i++) { if (_smartdns_prepare_server_flags(&flags, &dns_conf.servers[i]) != 0) { tlog(TLOG_ERROR, "prepare server flags failed, %s:%d", dns_conf.servers[i].server, dns_conf.servers[i].port); return -1; } ret = dns_client_add_server(dns_conf.servers[i].server, dns_conf.servers[i].port, dns_conf.servers[i].type, &flags); if (ret != 0) { tlog(TLOG_ERROR, "add server failed, %s:%d", dns_conf.servers[i].server, dns_conf.servers[i].port); return -1; } } hash_for_each(dns_group_table.group, i, group, node) { ret = dns_client_add_group(group->group_name); if (ret != 0) { tlog(TLOG_ERROR, "add group failed, %s", group->group_name); return -1; } for (j = 0; j < group->server_num; j++) { server = group->servers[j]; if (server == NULL) { continue; } if (_smartdns_prepare_server_flags(&flags, server) != 0) { tlog(TLOG_ERROR, "prepare server flags failed, %s:%d", server->server, server->port); return -1; } ret = dns_client_add_to_group(group->group_name, server->server, server->port, server->type, &flags); if (ret != 0) { tlog(TLOG_ERROR, "add server %s to group %s failed", server->server, group->group_name); return -1; } } } return 0; } static int _proxy_add_servers(void) { unsigned long i = 0; struct hlist_node *tmp = NULL; struct dns_proxy_names *proxy = NULL; struct dns_proxy_servers *server = NULL; struct dns_proxy_servers *server_tmp = NULL; hash_for_each_safe(dns_proxy_table.proxy, i, tmp, proxy, node) { list_for_each_entry_safe(server, server_tmp, &proxy->server_list, list) { struct proxy_info info; memset(&info, 0, sizeof(info)); info.type = server->type; info.port = server->port; safe_strncpy(info.server, server->server, PROXY_MAX_IPLEN); safe_strncpy(info.username, server->username, PROXY_MAX_NAMELEN); safe_strncpy(info.password, server->password, PROXY_MAX_NAMELEN); info.use_domain = server->use_domain; proxy_add(proxy->proxy_name, &info); } } return 0; } static int _smartdns_plugin_init(void) { int ret = 0; unsigned long i = 0; struct dns_conf_plugin *plugin = NULL; struct hlist_node *tmp = NULL; ret = dns_server_plugin_init(); if (ret != 0) { tlog(TLOG_ERROR, "init plugin failed."); goto errout; } hash_for_each_safe(dns_conf_plugin_table.plugins, i, tmp, plugin, node) { ret = dns_plugin_add(plugin->file, plugin->argc, plugin->args, plugin->args_len); if (ret != 0) { goto errout; } } return 0; errout: return -1; } static int _smartdns_plugin_exit(void) { dns_server_plugin_exit(); return 0; } static int _smartdns_create_cert(void) { uid_t uid = 0; gid_t gid = 0; char san[PATH_MAX] = {0}; /* 13 month */ int validity_days = 13 * 30; char ddns_san[DNS_MAX_CNAME_LEN] = {0}; if (dns_conf.need_cert == 0) { return 0; } if (dns_conf.bind_ca_file[0] != 0 && dns_conf.bind_ca_key_file[0] != 0) { return 0; } conf_get_conf_fullpath("smartdns-cert.pem", dns_conf.bind_ca_file, sizeof(dns_conf.bind_ca_file)); conf_get_conf_fullpath("smartdns-key.pem", dns_conf.bind_ca_key_file, sizeof(dns_conf.bind_ca_key_file)); conf_get_conf_fullpath("smartdns-root-key.pem", dns_conf.bind_root_ca_key_file, sizeof(dns_conf.bind_root_ca_key_file)); if (access(dns_conf.bind_ca_file, F_OK) == 0 && access(dns_conf.bind_ca_key_file, F_OK) == 0) { if (is_cert_valid(dns_conf.bind_ca_file)) { return 0; } if (access(dns_conf.bind_root_ca_key_file, R_OK) != 0) { tlog(TLOG_WARN, "root ca key file %s is not found, can not regenerate cert file.", dns_conf.bind_root_ca_key_file); return 0; } unlink(dns_conf.bind_ca_file); unlink(dns_conf.bind_ca_key_file); tlog(TLOG_WARN, "regenerate cert with root ca key %s", dns_conf.bind_root_ca_key_file); } if (dns_conf_get_ddns_domain()[0] != 0) { snprintf(ddns_san, sizeof(ddns_san), "DNS:%s", dns_conf_get_ddns_domain()); } if (generate_cert_san(san, sizeof(san), ddns_san) != 0) { tlog(TLOG_WARN, "generate cert san failed."); return -1; } if (dns_conf.bind_ca_validity_days > 0) { validity_days = dns_conf.bind_ca_validity_days; } if (generate_cert_key(dns_conf.bind_ca_key_file, dns_conf.bind_ca_file, dns_conf.bind_root_ca_key_file, san, validity_days) != 0) { tlog(TLOG_WARN, "Generate default ssl cert and key file failed. %s", strerror(errno)); return -1; } int unused __attribute__((unused)) = 0; if (get_uid_gid(&uid, &gid) != 0) { return 0; } unused = chown(dns_conf.bind_ca_file, uid, gid); unused = chown(dns_conf.bind_ca_key_file, uid, gid); return 0; } int smartdns_get_cert(char *key, char *cert) { if (dns_conf.need_cert == 0) { dns_conf.need_cert = 1; } if (_smartdns_create_cert() != 0) { tlog(TLOG_WARN, "generate ssl cert and key file failed. %s", strerror(errno)); return -1; } if (key != NULL) { safe_strncpy(key, dns_conf.bind_ca_key_file, PATH_MAX); } if (cert != NULL) { safe_strncpy(cert, dns_conf.bind_ca_file, PATH_MAX); } return 0; } static int _smartdns_init_ssl(void) { #if OPENSSL_API_COMPAT < 0x10100000L SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); SSL_CRYPTO_thread_setup(); #endif return 0; } static int _smartdns_destroy_ssl(void) { #if OPENSSL_API_COMPAT < 0x10100000L SSL_CRYPTO_thread_cleanup(); ERR_free_strings(); EVP_cleanup(); #endif return 0; } static const char *_smartdns_log_path(void) { char *logfile = SMARTDNS_LOG_FILE; if (dns_conf.log_file[0] != 0) { logfile = dns_conf.log_file; } return logfile; } static int _smartdns_tlog_output_syslog_callback(struct tlog_loginfo *info, const char *buff, int bufflen, void *private_data) { int syslog_level = LOG_INFO; switch (info->level) { case TLOG_ERROR: syslog_level = LOG_ERR; break; case TLOG_WARN: syslog_level = LOG_WARNING; break; case TLOG_NOTICE: syslog_level = LOG_NOTICE; break; case TLOG_INFO: syslog_level = LOG_INFO; break; case TLOG_DEBUG: syslog_level = LOG_DEBUG; break; default: syslog_level = LOG_INFO; break; } syslog(syslog_level, "%.*s", bufflen, buff); return bufflen; } static int _smartdns_tlog_output_callback(struct tlog_loginfo *info, const char *buff, int bufflen, void *private_data) { smartdns_plugin_func_server_log_callback((smartdns_log_level)info->level, buff, bufflen); if (dns_conf.log_syslog) { return _smartdns_tlog_output_syslog_callback(info, buff, bufflen, private_data); } return tlog_write_log(buff, bufflen); } static int _smartdns_init_log(void) { const char *logfile = _smartdns_log_path(); char logdir[PATH_MAX] = {0}; int logbuffersize = 0; int enable_log_screen = 0; int ret = 0; if (get_system_mem_size() > 1024 * 1024 * 1024) { logbuffersize = 1024 * 1024; } safe_strncpy(logdir, _smartdns_log_path(), PATH_MAX); if (verbose_screen != 0 || dns_conf.log_console != 0 || access(dir_name(logdir), W_OK) != 0) { enable_log_screen = 1; } unsigned int tlog_flag = TLOG_NONBLOCK; if (enable_log_screen == 1) { tlog_flag |= TLOG_SCREEN; } if (dns_conf.log_color_mode) { tlog_flag |= TLOG_SEGMENT; if (enable_log_screen) { tlog_flag |= TLOG_SCREEN_COLOR; } } if (dns_conf.log_syslog) { tlog_flag |= TLOG_SEGMENT; tlog_flag |= TLOG_FORMAT_NO_PREFIX; } ret = tlog_init(logfile, dns_conf.log_size, dns_conf.log_num, logbuffersize, tlog_flag); if (ret != 0) { tlog(TLOG_ERROR, "start tlog failed.\n"); goto errout; } if (enable_log_screen) { tlog_setlogscreen(1); verbose_screen = 1; } tlog_reg_log_output_func(_smartdns_tlog_output_callback, NULL); tlog_setlevel(dns_conf.log_level); if (dns_conf.log_file_mode > 0) { tlog_set_permission(tlog_get_root(), dns_conf.log_file_mode, dns_conf.log_file_mode); } return 0; errout: return -1; } static int _smartdns_init_load_from_resolv(void) { int ret = 0; int i = 0; for (i = 0; i < 180 && dns_conf.server_num <= 0; i++) { ret = _smartdns_load_from_resolv(); if (ret == 0) { continue; } /* try load from default resolv.conf file */ if (i > 30 && strncmp(dns_conf.dns_resolv_file, DNS_RESOLV_FILE, MAX_LINE_LEN) != 0) { ret = _smartdns_load_from_default_resolv(); if (ret == 0) { continue; } } tlog(TLOG_DEBUG, "load dns from resolv failed, retry after 1s, retry times %d.", i + 1); sleep(1); } if (dns_conf.server_num <= 0) { goto errout; } return 0; errout: return -1; } static int _smartdns_init(void) { int ret = 0; char str_ver[256] = {0}; if (_smartdns_init_log() != 0) { tlog(TLOG_ERROR, "init log failed."); goto errout; } _smartdns_get_version(str_ver, sizeof(str_ver)); tlog(TLOG_NOTICE, "smartdns starting...(Copyright (C) Nick Peng , build: %s)", str_ver); if (dns_timer_init() != 0) { tlog(TLOG_ERROR, "init timer failed."); goto errout; } if (_smartdns_init_ssl() != 0) { tlog(TLOG_ERROR, "init ssl failed."); goto errout; } if (_smartdns_init_load_from_resolv() != 0) { tlog(TLOG_ERROR, "no dns server found, exit..."); goto errout; } ret = fast_ping_init(); if (ret != 0) { tlog(TLOG_ERROR, "start ping failed.\n"); goto errout; } ret = proxy_init(); if (ret != 0) { tlog(TLOG_ERROR, "start proxy failed.\n"); goto errout; } ret = _proxy_add_servers(); if (ret != 0) { tlog(TLOG_ERROR, "add proxy servers failed."); } ret = dns_stats_init(); if (ret != 0) { tlog(TLOG_ERROR, "start dns stats failed.\n"); goto errout; } ret = dns_server_init(); if (ret != 0) { tlog(TLOG_ERROR, "start dns server failed.\n"); goto errout; } ret = dns_client_init(); if (ret != 0) { tlog(TLOG_ERROR, "start dns client failed.\n"); goto errout; } ret = _smartdns_add_servers(); if (ret != 0) { tlog(TLOG_ERROR, "add servers failed."); goto errout; } ret = _smartdns_plugin_init(); if (ret != 0) { tlog(TLOG_ERROR, "init plugin failed."); goto errout; } return 0; errout: return -1; } static int _smartdns_run(void) { return dns_server_run(); } static void _smartdns_exit(void) { _smartdns_plugin_exit(); proxy_exit(); fast_ping_exit(); dns_server_exit(); dns_client_exit(); dns_stats_exit(); _smartdns_destroy_ssl(); dns_timer_destroy(); tlog_exit(); dns_server_load_exit(); } static void _sig_exit(int signo) { tlog(TLOG_INFO, "stop smartdns by signal %d", signo); dns_server_stop(); } static void _sig_error_exit(int signo, siginfo_t *siginfo, void *ct) { unsigned long PC = 0; ucontext_t *context = ct; const char *arch = NULL; const char *build_info = ""; #ifdef SMARTDNS_VERION build_info = SMARTDNS_VERION; #else build_info = __DATE__ " " __TIME__; #endif #if defined(__i386__) int *pgregs = (int *)(&(context->uc_mcontext.gregs)); PC = pgregs[REG_EIP]; arch = "i386"; #elif defined(__x86_64__) int *pgregs = (int *)(&(context->uc_mcontext.gregs)); PC = pgregs[REG_RIP]; arch = "x86_64"; #elif defined(__arm__) PC = context->uc_mcontext.arm_pc; arch = "arm"; #elif defined(__aarch64__) PC = context->uc_mcontext.pc; arch = "arm64"; #elif defined(__mips__) PC = context->uc_mcontext.pc; arch = "mips"; #endif tlog(TLOG_FATAL, "process exit with signal %d, code = %d, errno = %d, pid = %d, self = %d, pc = %#lx, addr = %#lx, build(" "%s %s)\n", signo, siginfo->si_code, siginfo->si_errno, siginfo->si_pid, getpid(), PC, (unsigned long)siginfo->si_addr, build_info, arch); print_stack(); sleep(1); _exit(SMARTDNS_CRASH_CODE); } static int sig_list[] = {SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGFPE}; static int sig_num = sizeof(sig_list) / sizeof(int); static void _reg_signal(void) { struct sigaction act; struct sigaction old; int i = 0; act.sa_sigaction = _sig_error_exit; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART | SA_SIGINFO; for (i = 0; i < sig_num; i++) { sigaction(sig_list[i], &act, &old); } } static int _smartdns_create_logdir(void) { int ret = create_dir_with_perm(_smartdns_log_path()); if (ret == -2) { tlog_set_maxlog_count(0); } else if (ret != 0) { return -1; } return 0; } static int _smartdns_create_cache_dir(void) { int ret = create_dir_with_perm(dns_conf_get_cache_dir()); if (ret == -2) { if (dns_conf.cache_file[0] == '\0') { safe_strncpy(dns_conf.cache_file, SMARTDNS_TMP_CACHE_FILE, sizeof(dns_conf.cache_file)); } } else if (ret != 0) { return -1; } return 0; } static int _smartdns_create_datadir(void) { uid_t uid = 0; gid_t gid = 0; struct stat sb; char data_dir[PATH_MAX] = {0}; int unused __attribute__((unused)) = 0; safe_strncpy(data_dir, dns_conf_get_data_dir(), PATH_MAX); if (get_uid_gid(&uid, &gid) != 0) { return -1; } mkdir(data_dir, 0750); if (stat(data_dir, &sb) != 0) { tlog(TLOG_DEBUG, "create dir %s failed, %s", data_dir, strerror(errno)); return -1; } if (sb.st_uid == uid && sb.st_gid == gid && (sb.st_mode & 0700) == 0700) { return 0; } if (chown(data_dir, uid, gid) != 0) { if (dns_conf.cache_file[0] == '\0') { safe_strncpy(dns_conf.cache_file, SMARTDNS_DATA_DIR, sizeof(dns_conf.cache_file)); } } unused = chmod(data_dir, 0750); unused = chown(dns_conf_get_data_dir(), uid, gid); return 0; } static int _set_rlimit(void) { struct rlimit value; value.rlim_cur = 40; value.rlim_max = 40; setrlimit(RLIMIT_NICE, &value); value.rlim_cur = 1024 * 10; value.rlim_max = 1024 * 10; setrlimit(RLIMIT_NOFILE, &value); return 0; } static int _smartdns_init_pre(void) { int ret = -1; _smartdns_create_logdir(); _smartdns_create_cache_dir(); ret = _smartdns_create_datadir(); if (ret != 0) { tlog(TLOG_DEBUG, "create data dir failed."); } _set_rlimit(); if (_smartdns_create_cert() != 0) { tlog(TLOG_ERROR, "create cert failed."); return -1; } return 0; } static int _smartdns_child_pid = 0; static int _smartdns_child_restart = 0; static void _smartdns_run_monitor_sig(int sig) { if (_smartdns_child_pid > 0) { if (sig == SIGHUP) { _smartdns_child_restart = 1; kill(_smartdns_child_pid, SIGTERM); return; } kill(_smartdns_child_pid, SIGTERM); } waitpid(_smartdns_child_pid, NULL, 0); _exit(0); } static smartdns_run_monitor_ret _smartdns_run_monitor(int restart_when_crash, int is_run_as_daemon) { pid_t pid; int status; if (restart_when_crash == 0) { return SMARTDNS_RUN_MONITOR_OK; } if (is_run_as_daemon) { switch (daemon_run(NULL)) { case DAEMON_RET_CHILD_OK: break; case DAEMON_RET_PARENT_OK: return SMARTDNS_RUN_MONITOR_EXIT; default: return SMARTDNS_RUN_MONITOR_ERROR; } } daemon_kickoff(0, 1); restart: pid = fork(); if (pid < 0) { fprintf(stderr, "fork failed, %s\n", strerror(errno)); return SMARTDNS_RUN_MONITOR_ERROR; } else if (pid == 0) { return SMARTDNS_RUN_MONITOR_OK; } _smartdns_child_pid = pid; signal(SIGTERM, _smartdns_run_monitor_sig); signal(SIGHUP, _smartdns_run_monitor_sig); while (true) { pid = waitpid(-1, &status, 0); if (pid == _smartdns_child_pid) { int need_restart = 0; char signalmsg[64] = {0}; if (_smartdns_child_restart == 1) { _smartdns_child_restart = 0; goto restart; } if (WEXITSTATUS(status) == SMARTDNS_CRASH_CODE) { need_restart = 1; } else if (WEXITSTATUS(status) == 255) { fprintf(stderr, "run daemon failed, please check log.\n"); } else if (WIFSIGNALED(status)) { switch (WTERMSIG(status)) { case SIGSEGV: case SIGABRT: case SIGBUS: case SIGILL: case SIGFPE: snprintf(signalmsg, sizeof(signalmsg), " with signal %d", WTERMSIG(status)); need_restart = 1; break; default: break; } } if (need_restart == 1) { printf("smartdns crashed%s, restart...\n", signalmsg); goto restart; } break; } if (pid < 0) { sleep(1); } } return SMARTDNS_RUN_MONITOR_ERROR; } static void _smartdns_print_error_tip(void) { char buff[4096]; char *log_path = realpath(_smartdns_log_path(), buff); if (log_path != NULL && access(log_path, F_OK) == 0) { fprintf(stderr, "run daemon failed, please check log at %s\n", log_path); } } void smartdns_exit(int status) { dns_server_stop(); exit_status = status; } void smartdns_restart(void) { exit_restart = 1; dns_server_stop(); } static const char *smartdns_exec_dir(void) { static char start_dir[PATH_MAX] = {0}; if (start_dir[0] == 0) { if (getcwd(start_dir, sizeof(start_dir)) == NULL) { snprintf(start_dir, sizeof(start_dir), "."); } } return start_dir; } static int smartdns_enter_monitor_mode(int argc, char *argv[], int no_deamon) { char exec_path[PATH_MAX] = {0}; setenv("SMARTDNS_RESTART_ON_CRASH", "1", 1); if (no_deamon == 1) { setenv("SMARTDNS_NO_DAEMON", "1", 1); } chdir(smartdns_exec_dir()); if (readlink("/proc/self/exe", exec_path, sizeof(exec_path) - 1) > 0) { execv(exec_path, argv); } else { safe_strncpy(exec_path, argv[0], sizeof(exec_path)); execvp(exec_path, argv); } tlog(TLOG_ERROR, "execv failed, %s, %s", exec_path, strerror(errno)); return -1; } static int smartdns_init_workdir(void) { smartdns_exec_dir(); const char *smartdns_workdir = getenv("SMARTDNS_WORKDIR"); if (smartdns_workdir != NULL) { if (chdir(smartdns_workdir) != 0) { fprintf(stderr, "chdir to %s failed: %s\n", smartdns_workdir, strerror(errno)); return 1; } } return 0; } #ifdef TEST static smartdns_post_func _smartdns_post = NULL; static void *_smartdns_post_arg = NULL; int smartdns_reg_post_func(smartdns_post_func func, void *arg) { _smartdns_post = func; _smartdns_post_arg = arg; return 0; } #define smartdns_test_notify(retval) smartdns_test_notify_func(fd_notify, retval) static void smartdns_test_notify_func(int fd_notify, uint64_t retval) { int unused __attribute__((unused)); /* notify parent kickoff */ if (fd_notify > 0) { unused = write(fd_notify, &retval, sizeof(retval)); } if (_smartdns_post != NULL) { _smartdns_post(_smartdns_post_arg); } } #define smartdns_close_allfds() \ if (no_close_allfds == 0) { \ close_all_fd(fd_notify); \ } int smartdns_test_main(int argc, char *argv[], int fd_notify, int no_close_allfds) #else #define smartdns_test_notify(retval) #define smartdns_close_allfds() close_all_fd(-1) int smartdns_main(int argc, char *argv[]) #endif { int ret = 0; int is_run_as_daemon = 1; int opt = 0; char config_file[MAX_LINE_LEN]; char pid_file[MAX_LINE_LEN]; int is_pid_file_set = 0; int signal_ignore = 0; int restart_when_crash = getpid() == 1 ? 1 : 0; sigset_t empty_sigblock; struct stat sb; static struct option long_options[] = {{"cache-print", required_argument, NULL, 256}, {"is-quic-supported", no_argument, NULL, 257}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; if (smartdns_init_workdir() != 0) { return 1; } safe_strncpy(config_file, SMARTDNS_CONF_FILE, MAX_LINE_LEN); if (stat("/run", &sb) == 0 && S_ISDIR(sb.st_mode)) { safe_strncpy(pid_file, SMARTDNS_PID_FILE, MAX_LINE_LEN); } else { safe_strncpy(pid_file, SMARTDNS_LEGACY_PID_FILE, MAX_LINE_LEN); } /* patch for Asus router: unblock all signal*/ sigemptyset(&empty_sigblock); sigprocmask(SIG_SETMASK, &empty_sigblock, NULL); smartdns_close_allfds(); while ((opt = getopt_long(argc, argv, "fhc:p:SvxN:R", long_options, NULL)) != -1) { switch (opt) { case 'f': is_run_as_daemon = 0; break; case 'c': if (full_path(config_file, sizeof(config_file), optarg) != 0) { snprintf(config_file, sizeof(config_file), "%s", optarg); } break; case 'p': if (strncmp(optarg, "-", 2) == 0 || full_path(pid_file, sizeof(pid_file), optarg) != 0) { snprintf(pid_file, sizeof(pid_file), "%s", optarg); is_pid_file_set = 1; } break; case 'R': restart_when_crash = 1; break; case 'S': signal_ignore = 1; break; case 'x': verbose_screen = 1; break; case 'v': _show_version(); return 0; break; #ifdef DEBUG case 'N': return dns_packet_debug(optarg); #endif case 'h': _help(); return 0; case 256: tlog_set_early_printf(1, 1, 1); return dns_cache_print(optarg); break; case 257: if (dns_is_quic_supported() == 0) { fprintf(stdout, "quic is not supported.\n"); return 1; } else { fprintf(stdout, "quic is supported.\n"); return 0; } return 0; break; default: fprintf(stderr, "unknown option, please run %s -h for help.\n", argv[0]); return 1; } } if (getenv("SMARTDNS_RESTART_ON_CRASH") != NULL) { restart_when_crash = 1; unsetenv("SMARTDNS_RESTART_ON_CRASH"); } if (getenv("SMARTDNS_NO_DAEMON") != NULL) { is_run_as_daemon = 0; unsetenv("SMARTDNS_NO_DAEMON"); } /* started by systemd, do not restart when crash */ if (getenv("INVOCATION_ID") != NULL) { restart_when_crash = 0; } smartdns_run_monitor_ret init_ret = _smartdns_run_monitor(restart_when_crash, is_run_as_daemon); if (init_ret != SMARTDNS_RUN_MONITOR_OK) { if (init_ret == SMARTDNS_RUN_MONITOR_EXIT) { return 0; } return 1; } srand(time(NULL)); tlog_set_early_printf(1, 1, 1); tlog_reg_early_printf_output_callback(_smartdns_tlog_output_syslog_callback, 1, NULL); ret = dns_server_load_conf(config_file); if (ret != 0) { fprintf(stderr, "load config failed.\n"); goto errout; } /* started by systemd, do not restart when crash */ if (getenv("INVOCATION_ID") != NULL) { dns_conf.dns_restart_on_crash = 0; } if (dns_conf.dns_restart_on_crash && restart_when_crash == 0) { return smartdns_enter_monitor_mode(argc, argv, dns_conf.dns_no_daemon || !is_run_as_daemon); } if (dns_conf.dns_no_daemon || restart_when_crash) { is_run_as_daemon = 0; } if (is_run_as_daemon) { int child_status = -1; switch (daemon_run(&child_status)) { case DAEMON_RET_CHILD_OK: break; case DAEMON_RET_PARENT_OK: { if (child_status != 0 && child_status != -3) { _smartdns_print_error_tip(); } return child_status; } break; case DAEMON_RET_ERR: default: fprintf(stderr, "run daemon failed.\n"); goto errout; } } if (signal_ignore == 0) { _reg_signal(); } if (is_pid_file_set == 0) { char pid_file_path[MAX_LINE_LEN]; safe_strncpy(pid_file_path, pid_file, MAX_LINE_LEN); dir_name(pid_file_path); if (access(pid_file_path, W_OK) != 0) { dns_conf.dns_no_pidfile = 1; } } if (strncmp(pid_file, "-", 2) != 0 && dns_conf.dns_no_pidfile == 0 && create_pid_file(pid_file) != 0) { ret = -3; goto errout; } signal(SIGPIPE, SIG_IGN); signal(SIGINT, _sig_exit); signal(SIGTERM, _sig_exit); ret = _smartdns_init_pre(); if (ret != 0) { fprintf(stderr, "smartdns init failed.\n"); goto errout; } drop_root_privilege(); ret = _smartdns_init(); if (ret != 0) { usleep(100000); goto errout; } if (is_run_as_daemon) { ret = daemon_kickoff(0, dns_conf.log_console | dns_conf.audit_console | verbose_screen); if (ret != 0) { goto errout; } } else if (dns_conf.log_console == 0 && dns_conf.audit_console == 0 && verbose_screen == 0) { daemon_close_stdfds(); } smartdns_test_notify(1); ret = _smartdns_run(); if (ret == 0 && exit_status != 0) { ret = exit_status; } if (exit_restart == 0) { tlog(TLOG_INFO, "smartdns exit..."); _smartdns_exit(); } else { tlog(TLOG_INFO, "smartdns restart..."); _smartdns_exit(); if (restart_when_crash == 0) { execve(argv[0], argv, environ); } } return ret; errout: if (is_run_as_daemon) { daemon_kickoff(ret, dns_conf.log_console | dns_conf.audit_console | verbose_screen); } else if (dns_conf.log_console == 0 && dns_conf.audit_console == 0 && verbose_screen == 0) { _smartdns_print_error_tip(); } smartdns_test_notify(2); _smartdns_exit(); return ret; } int smartdns_server_run(const char *config_file) { int ret = -1; ret = dns_server_load_conf(config_file); if (ret != 0) { fprintf(stderr, "load config failed.\n"); goto errout; } ret = _smartdns_init_pre(); if (ret != 0) { fprintf(stderr, "init failed.\n"); goto errout; } ret = _smartdns_init(); if (ret != 0) { fprintf(stderr, "init failed.\n"); goto errout; } ret = _smartdns_run(); if (ret != 0) { fprintf(stderr, "run failed.\n"); goto errout; } _smartdns_exit(); tlog(TLOG_INFO, "smartdns exit..."); return ret; errout: _smartdns_exit(); return -1; } int smartdns_server_stop(void) { dns_server_stop(); return 0; } ================================================ FILE: src/timer.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/timer.h" #include "smartdns/lib/timer_wheel.h" static struct tw_base *dns_timer_base = NULL; int dns_timer_init(void) { struct tw_base *tw = tw_init_timers(); if (tw == NULL) { return -1; } dns_timer_base = tw; return 0; } void dns_timer_destroy(void) { if (dns_timer_base != NULL) { tw_cleanup_timers(dns_timer_base); dns_timer_base = NULL; } } void dns_timer_add(struct tw_timer_list *timer) { if (dns_timer_base == NULL) { return; } tw_add_timer(dns_timer_base, timer); } int dns_timer_del(struct tw_timer_list *timer) { if (dns_timer_base == NULL) { return 0; } return tw_del_timer(dns_timer_base, timer); } int dns_timer_mod(struct tw_timer_list *timer, unsigned long expires) { if (dns_timer_base == NULL) { return 0; } return tw_mod_timer_pending(dns_timer_base, timer, expires); } ================================================ FILE: src/tlog.c ================================================ /* * tinylog * Copyright (C) 2018-2025 Nick Peng * https://github.com/pymumu/tinylog */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "smartdns/tlog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef likely #define likely(x) __builtin_expect(!!(x), 1) #endif #ifndef unlikely #define unlikely(x) __builtin_expect(!!(x), 0) #endif #define TLOG_BUFF_SIZE (1024 * 128) #define TLOG_TMP_LEN 128 #define TLOG_LOG_SIZE (1024 * 1024 * 50) #define TLOG_LOG_COUNT 32 #define TLOG_LOG_NAME_LEN 256 #define TLOG_BUFF_LEN (PATH_MAX + TLOG_LOG_NAME_LEN * 3) #define TLOG_SUFFIX_GZ ".gz" #define TLOG_SUFFIX_LOG "" #define TLOG_MAX_LINE_SIZE_SET (1024 * 8) #define TLOG_MIN_LINE_SIZE_SET (128) #define TLOG_SEGMENT_MAGIC 0xFF446154 struct linux_dirent64 { unsigned long long d_ino; long long d_off; unsigned short d_reclen; unsigned char d_type; char d_name[256]; }; struct tlog_log { char *buff; int buffsize; int start; int end; int ext_end; int fd; int fd_lock; off_t filesize; char logdir[PATH_MAX]; char logname[TLOG_LOG_NAME_LEN]; char suffix[TLOG_LOG_NAME_LEN]; char pending_logfile[PATH_MAX]; char logfile[PATH_MAX * 2]; int rename_pending; int fail; int logsize; int logcount; int block; int dropped; int nocompress; int zip_pid; int multi_log; int logscreen; int logscreen_color; int segment_log; int max_line_size; int print_errmsg; tlog_output_func output_func; void *private_data; int set_custom_output_func; time_t last_try; time_t last_waitpid; mode_t file_perm; mode_t archive_perm; int mode_changed; int waiters; int is_exit; struct tlog_log *next; pthread_mutex_t lock; pthread_cond_t client_cond; }; struct tlog { struct tlog_log *root; struct tlog_log *log; struct tlog_log *notify_log; int run; pthread_t tid; pthread_mutex_t lock; pthread_cond_t cond; tlog_log_output_func output_func; struct tlog_log *wait_on_log; int is_wait; int output_no_prefix; char gzip_cmd[PATH_MAX]; tlog_format_func root_format; tlog_early_print_func tlog_early_print; tlog_log_output_func early_print_output; int early_print_disable; int early_print_with_screen; int early_print_no_prefix; int early_print_color; void *early_print_userptr; }; struct tlog_segment_log_head { struct tlog_loginfo info; unsigned short len; char data[0]; } __attribute__((packed)); struct tlog_segment_head { unsigned int magic; unsigned short len; char data[0]; } __attribute__((packed)); struct oldest_log { char name[TLOG_LOG_NAME_LEN]; time_t mtime; struct tlog_log *log; }; struct count_log { int lognum; struct tlog_log *log; }; struct tlog_info_inter { struct tlog_loginfo info; void *userptr; }; typedef int (*list_callback)(const char *name, struct dirent *entry, void *user); typedef int (*vprint_callback)(char *buff, int maxlen, void *userptr, const char *format, va_list ap); static struct tlog tlog; static tlog_level tlog_set_level = TLOG_INFO; unsigned int tlog_localtime_lock; static const char *tlog_level_str[] = { "DEBUG", "INFO", "NOTICE", "WARN", "ERROR", "FATAL", }; static inline void _tlog_spin_lock(unsigned int *lock) { while (1) { int i; for (i = 0; i < 10000; i++) { if (__sync_bool_compare_and_swap(lock, 0, 1)) { return; } } sched_yield(); } } static inline void _tlog_spin_unlock(unsigned int *lock) { __sync_bool_compare_and_swap(lock, 1, 0); } static int _tlog_mkdir(const char *path) { char path_c[PATH_MAX + 1]; char *path_end; char str; int len; if (access(path, F_OK) == 0) { return 0; } while (*path == ' ') { path++; } strncpy(path_c, path, sizeof(path_c) - 1); path_c[sizeof(path_c) - 1] = '\0'; len = strnlen(path_c, sizeof(path_c) - 1); path_c[len] = '/'; path_c[len + 1] = '\0'; path_end = path_c; /* create directory recursively */ while (*path_end != 0) { if (*path_end != '/') { path_end++; continue; } if (path_end == path_c) { path_end++; continue; } str = *path_end; *path_end = '\0'; if (access(path_c, F_OK) == 0) { *path_end = str; path_end++; continue; } if (mkdir(path_c, 0750) != 0) { return -1; } *path_end = str; path_end++; } return 0; } static struct tm *_tlog_localtime(time_t *timep, struct tm *tm) { static time_t last_time; static struct tm last_tm; /* localtime_r has a global timezone lock, it's about 8 times slower than gmtime * this code is used to speed up localtime_r call. */ _tlog_spin_lock(&tlog_localtime_lock); if (*timep == last_time) { *tm = last_tm; } else { _tlog_spin_unlock(&tlog_localtime_lock); tm = localtime_r(timep, tm); _tlog_spin_lock(&tlog_localtime_lock); if (tm) { last_time = *timep; last_tm = *tm; } } _tlog_spin_unlock(&tlog_localtime_lock); return tm; } static int _tlog_getmtime(struct tlog_time *log_mtime, const char *file) { struct tm tm; struct stat sb; if (stat(file, &sb) != 0) { return -1; } if (_tlog_localtime(&sb.st_mtime, &tm) == NULL) { return -1; } log_mtime->year = tm.tm_year + 1900; log_mtime->mon = tm.tm_mon + 1; log_mtime->mday = tm.tm_mday; log_mtime->hour = tm.tm_hour; log_mtime->min = tm.tm_min; log_mtime->sec = tm.tm_sec; log_mtime->usec = 0; return 0; } static int _tlog_gettime(struct tlog_time *cur_time) { struct tm tm; struct timeval tmval; if (gettimeofday(&tmval, NULL) != 0) { return -1; } if (_tlog_localtime(&tmval.tv_sec, &tm) == NULL) { return -1; } cur_time->year = tm.tm_year + 1900; cur_time->mon = tm.tm_mon + 1; cur_time->mday = tm.tm_mday; cur_time->hour = tm.tm_hour; cur_time->min = tm.tm_min; cur_time->sec = tm.tm_sec; cur_time->usec = tmval.tv_usec; return 0; } void tlog_set_maxline_size(struct tlog_log *log, int size) { if (log == NULL) { return; } if (size < TLOG_MIN_LINE_SIZE_SET) { size = TLOG_MIN_LINE_SIZE_SET; } else if (size > TLOG_MAX_LINE_SIZE_SET) { size = TLOG_MAX_LINE_SIZE_SET; } log->max_line_size = size; } void tlog_logcount(struct tlog_log *log, int count) { if (log == NULL) { return; } if (count < 0) { count = 0; } log->logcount = count; } void tlog_set_permission(struct tlog_log *log, mode_t file, mode_t archive) { log->file_perm = file; log->archive_perm = archive; log->mode_changed = 1; } int tlog_localtime(struct tlog_time *tm) { return _tlog_gettime(tm); } tlog_log *tlog_get_root(void) { return tlog.root; } void tlog_set_private(tlog_log *log, void *private_data) { if (log == NULL) { return; } log->private_data = private_data; } void *tlog_get_private(tlog_log *log) { if (log == NULL) { return NULL; } return log->private_data; } static int _tlog_root_default_format(char *buff, int maxlen, struct tlog_loginfo *info, void *userptr, const char *format, va_list ap) { int len = 0; int total_len = 0; struct tlog_time *tm = &info->time; void *unused __attribute__((unused)); unused = userptr; if (tlog.output_no_prefix == 0) { if (tlog.root->multi_log) { /* format prefix */ len = snprintf(buff, maxlen, "[%.4d-%.2d-%.2d %.2d:%.2d:%.2d,%.3d][%5d][%4s][%17s:%-4d] ", tm->year, tm->mon, tm->mday, tm->hour, tm->min, tm->sec, tm->usec / 1000, getpid(), tlog_get_level_string(info->level), info->file, info->line); } else { /* format prefix */ len = snprintf(buff, maxlen, "[%.4d-%.2d-%.2d %.2d:%.2d:%.2d,%.3d][%5s][%17s:%-4d] ", tm->year, tm->mon, tm->mday, tm->hour, tm->min, tm->sec, tm->usec / 1000, tlog_get_level_string(info->level), info->file, info->line); } } if (len < 0 || len >= maxlen) { return -1; } buff += len; total_len += len; maxlen -= len; /* format log message */ len = vsnprintf(buff, maxlen, format, ap); if (len < 0 || len == maxlen) { return -1; } buff += len; total_len += len; /* return total length */ return total_len; } static int _tlog_root_log_buffer(char *buff, int maxlen, void *userptr, const char *format, va_list ap) { int len = 0; int log_len = 0; struct tlog_info_inter *info_inter = (struct tlog_info_inter *)userptr; struct tlog_segment_log_head *log_head = NULL; int max_format_len = 0; if (tlog.root_format == NULL) { return -1; } if (tlog.root->segment_log) { log_head = (struct tlog_segment_log_head *)buff; len += sizeof(*log_head); memcpy(&log_head->info, &info_inter->info, sizeof(log_head->info)); } max_format_len = maxlen - len - 2; buff[maxlen - 1] = 0; log_len = tlog.root_format(buff + len, max_format_len, &info_inter->info, info_inter->userptr, format, ap); if (log_len < 0) { return -1; } else if (log_len >= max_format_len) { buff[len + max_format_len - 2] = '.'; buff[len + max_format_len - 3] = '.'; buff[len + max_format_len - 4] = '.'; log_len = max_format_len - 1; } len += log_len; /* add new line character*/ if (*(buff + len - 1) != '\n' && len + 1 < maxlen - 1) { *(buff + len) = '\n'; len++; log_len++; } if (tlog.root->segment_log && log_head != NULL) { if (len + 1 < maxlen - 1) { *(buff + len) = '\0'; len++; } log_head->len = log_len; } return len; } static int _tlog_print_buffer(char *buff, int maxlen, void *userptr, const char *format, va_list ap) { int len; int total_len = 0; void *unused __attribute__((unused)); unused = userptr; /* format log message */ len = vsnprintf(buff, maxlen, format, ap); if (len < 0 || len == maxlen) { return -1; } buff += len; total_len += len; /* return total length */ return total_len; } static int _tlog_need_drop(struct tlog_log *log) { int maxlen = 0; int ret = -1; if (log->block) { return -1; } pthread_mutex_lock(&tlog.lock); if (log->end == log->start) { if (log->ext_end == 0) { /* if buffer is empty */ maxlen = log->buffsize - log->end; } } else if (log->end > log->start) { maxlen = log->buffsize - log->end; } else { /* if reverse */ maxlen = log->start - log->end; } /* if free buffer length is less than min line length */ if (maxlen < log->max_line_size) { log->dropped++; ret = 0; } pthread_mutex_unlock(&tlog.lock); return ret; } static int _tlog_write_screen(struct tlog_log *log, struct tlog_loginfo *info, const char *buff, int bufflen) { if (bufflen <= 0) { return 0; } if (log->logscreen == 0) { return 0; } if (info == NULL) { return write(STDOUT_FILENO, buff, bufflen);; } return tlog_stdout_with_color(info->level, buff, bufflen); } static int _tlog_write_output_func(struct tlog_log *log, char *buff, int bufflen) { if (log->logscreen && log != tlog.root) { _tlog_write_screen(log, NULL, buff, bufflen); } if (log->output_func == NULL) { return -1; } return log->output_func(log, buff, bufflen); } static void _tlog_output_warning(void) { static int printed = 0; int unused __attribute__((unused)); if (printed) { return; } printed = 1; tlog_log *root = tlog.root; const char warning_msg[] = "" "TLOG ERROR: \n" " Do not call the tlog output function from within a registered tlog log output callback function.\n" " Recursively calling the log output function will cause tlog to fail to output logs and deadlock.\n"; if (root->logcount > 0 && root->logsize > 0 && root->logfile[0] != 0) { int fd = open(root->logfile, O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, root->file_perm); if (fd >= 0) { unused = write(fd, warning_msg, sizeof(warning_msg) - 1); close(fd); } } /* if open log file failed, print to stderr */ fprintf(stderr, "\033[31;1m%s\033[0m\n", warning_msg); return; } static int _tlog_vprintf(struct tlog_log *log, vprint_callback print_callback, void *userptr, const char *format, va_list ap) { int len; int maxlen = 0; struct tlog_segment_head *segment_head = NULL; if (log == NULL || format == NULL) { return -1; } char buff[log->max_line_size]; if (log->buff == NULL) { return -1; } if (unlikely(log->logcount <= 0 && log->logscreen == 0 && log->set_custom_output_func == 0)) { return 0; } if (_tlog_need_drop(log) == 0) { return -1; } len = print_callback(buff, sizeof(buff), userptr, format, ap); if (len <= 0) { return -1; } else if (len >= log->max_line_size) { len = log->max_line_size; buff[len - 1] = '\0'; buff[len - 2] = '\n'; buff[len - 3] = '.'; buff[len - 4] = '.'; buff[len - 5] = '.'; } /* Output log from tlog_worker thread context? this may crash from upper-level function. Try call printf output log. */ if (tlog.tid == pthread_self()) { _tlog_output_warning(); vprintf(format, ap); printf("\n"); return -1; } pthread_mutex_lock(&tlog.lock); do { if (log->end == log->start) { if (log->ext_end == 0) { /* if buffer is empty */ maxlen = log->buffsize - log->end; } } else if (log->end > log->start) { maxlen = log->buffsize - log->end; } else { /* if reverse */ maxlen = log->start - log->end; } /* if free buffer length is less than min line length */ if (maxlen < log->max_line_size) { if (log->end != log->start) { tlog.notify_log = log; pthread_cond_signal(&tlog.cond); } /* if drop message, increase statistics and return */ if (log->block == 0) { log->dropped++; pthread_mutex_unlock(&tlog.lock); return -1; } pthread_mutex_unlock(&tlog.lock); pthread_mutex_lock(&log->lock); log->waiters++; /* block wait for free buffer */ int ret = pthread_cond_wait(&log->client_cond, &log->lock); log->waiters--; pthread_mutex_unlock(&log->lock); if (ret < 0) { return -1; } pthread_mutex_lock(&tlog.lock); } } while (maxlen < log->max_line_size); if (log->segment_log) { segment_head = (struct tlog_segment_head *)(log->buff + log->end); memcpy(segment_head->data, buff, len); log->end += len + sizeof(*segment_head) + 1; segment_head->len = len + 1; segment_head->data[len] = '\0'; segment_head->magic = TLOG_SEGMENT_MAGIC; } else { /* write log to buffer */ memcpy(log->buff + log->end, buff, len); log->end += len; } /* if remain buffer is not enough for a line, move end to start of buffer. */ if (log->end > log->buffsize - log->max_line_size) { log->ext_end = log->end; log->end = 0; } if (tlog.is_wait) { tlog.notify_log = log; pthread_cond_signal(&tlog.cond); } pthread_mutex_unlock(&tlog.lock); return len; } int tlog_vprintf(struct tlog_log *log, const char *format, va_list ap) { return _tlog_vprintf(log, _tlog_print_buffer, NULL, format, ap); } int tlog_printf(struct tlog_log *log, const char *format, ...) { int len; va_list ap; va_start(ap, format); len = tlog_vprintf(log, format, ap); va_end(ap); return len; } int tlog_stdout_with_color(tlog_level level, const char *buff, int bufflen) { int unused __attribute__((unused)); const char *color = NULL; switch (level) { case TLOG_DEBUG: color = "\033[0;94m"; break; case TLOG_NOTICE: color = "\033[0;97m"; break; case TLOG_WARN: color = "\033[0;33m"; break; case TLOG_ERROR: color = "\033[0;31m"; break; case TLOG_FATAL: color = "\033[31;1m"; break; default: unused = write(STDOUT_FILENO, buff, bufflen); return bufflen; } if (color != NULL) { fprintf(stdout, "%s%.*s\033[0m\n", color, bufflen - 1, buff); } else { fprintf(stdout, "%s", buff); } return bufflen; } static int _tlog_early_print(struct tlog_info_inter *info_inter, const char *format, va_list ap) { char log_buf[TLOG_MAX_LINE_LEN]; size_t len = 0; size_t out_len = 0; struct tlog_time cur_time; int unused __attribute__((unused)); if (tlog.early_print_disable) { return 0; } if (_tlog_gettime(&cur_time) != 0) { return -1; } if (tlog.tlog_early_print != NULL) { tlog.tlog_early_print(&info_inter->info, format, ap); return out_len; } if (tlog.early_print_no_prefix == 0) { len = snprintf(log_buf, sizeof(log_buf), "[%.4d-%.2d-%.2d %.2d:%.2d:%.2d,%.3d][%5s][%17s:%-4d] ", cur_time.year, cur_time.mon, cur_time.mday, cur_time.hour, cur_time.min, cur_time.sec, cur_time.usec / 1000, tlog_get_level_string(info_inter->info.level), info_inter->info.file, info_inter->info.line); } out_len = len; len = vsnprintf(log_buf + out_len, sizeof(log_buf) - out_len - 1, format, ap); out_len += len; if (len <= 0) { return -1; } else if (len >= sizeof(log_buf) - 1) { out_len = sizeof(log_buf) - 1; } if (log_buf[out_len - 1] != '\n') { log_buf[out_len] = '\n'; out_len++; } if (out_len + 1 < sizeof(log_buf) - out_len - 1) { log_buf[out_len] = '\0'; } if (tlog.early_print_output != NULL) { len = tlog.early_print_output(&info_inter->info, log_buf, out_len, tlog.early_print_userptr); if (tlog.early_print_with_screen == 0) { return len; } } if (tlog.early_print_color) { unused = tlog_stdout_with_color(info_inter->info.level, log_buf, out_len); } else { unused = write(STDOUT_FILENO, log_buf, out_len); } return out_len; } int tlog_vext(tlog_level level, const char *file, int line, const char *func, void *userptr, const char *format, va_list ap) { struct tlog_info_inter info_inter; if (level < tlog_set_level) { return 0; } if (level >= TLOG_END) { return -1; } info_inter.info.file = file; info_inter.info.line = line; info_inter.info.func = func; info_inter.info.level = level; info_inter.userptr = userptr; if (_tlog_gettime(&info_inter.info.time) != 0) { return -1; } if (tlog.root == NULL) { return _tlog_early_print(&info_inter, format, ap); } if (unlikely(tlog.root->logsize <= 0 && tlog.root->logscreen == 0 && tlog.root->set_custom_output_func == 0)) { return 0; } return _tlog_vprintf(tlog.root, _tlog_root_log_buffer, &info_inter, format, ap); } int tlog_ext(tlog_level level, const char *file, int line, const char *func, void *userptr, const char *format, ...) { int len; va_list ap; va_start(ap, format); len = tlog_vext(level, file, line, func, userptr, format, ap); va_end(ap); return len; } static int _tlog_rename_logfile(struct tlog_log *log, const char *log_file) { char archive_file[TLOG_BUFF_LEN]; struct tlog_time logtime; int i = 0; if (_tlog_getmtime(&logtime, log_file) != 0) { return -1; } snprintf(archive_file, sizeof(archive_file), "%s/%s-%.4d%.2d%.2d-%.2d%.2d%.2d%s", log->logdir, log->logname, logtime.year, logtime.mon, logtime.mday, logtime.hour, logtime.min, logtime.sec, log->suffix); while (access(archive_file, F_OK) == 0) { i++; snprintf(archive_file, sizeof(archive_file), "%s/%s-%.4d%.2d%.2d-%.2d%.2d%.2d-%d%s", log->logdir, log->logname, logtime.year, logtime.mon, logtime.mday, logtime.hour, logtime.min, logtime.sec, i, log->suffix); } if (rename(log_file, archive_file) != 0) { return -1; } chmod(archive_file, log->archive_perm); return 0; } static int _tlog_list_dir(const char *path, list_callback callback, void *userptr) { DIR *dir = NULL; struct dirent *ent; int ret = 0; const char *unused __attribute__((unused)) = path; dir = opendir(path); if (dir == NULL) { fprintf(stderr, "tlog: open directory failed, %s\n", strerror(errno)); goto errout; } while ((ent = readdir(dir)) != NULL) { if (strncmp(".", ent->d_name, 2) == 0 || strncmp("..", ent->d_name, 3) == 0) { continue; } ret = callback(path, ent, userptr); if (ret != 0) { goto errout; } } closedir(dir); return 0; errout: if (dir) { closedir(dir); dir = NULL; } return -1; } static int _tlog_count_log_callback(const char *path, struct dirent *entry, void *userptr) { struct count_log *count_log = (struct count_log *)userptr; struct tlog_log *log = count_log->log; char logname[TLOG_LOG_NAME_LEN * 2]; const char *unused __attribute__((unused)) = path; if (strstr(entry->d_name, log->suffix) == NULL) { return 0; } snprintf(logname, sizeof(logname), "%s-", log->logname); int len = strnlen(logname, sizeof(logname)); if (strncmp(logname, entry->d_name, len) != 0) { return 0; } count_log->lognum++; return 0; } static int _tlog_get_oldest_callback(const char *path, struct dirent *entry, void *userptr) { struct stat sb; char filename[TLOG_BUFF_LEN]; struct oldest_log *oldestlog = (struct oldest_log *)userptr; struct tlog_log *log = oldestlog->log; char logname[TLOG_LOG_NAME_LEN * 2]; /* if not a log file, skip */ if (strstr(entry->d_name, log->suffix) == NULL) { return 0; } /* if not tlog log file, skip */ snprintf(logname, sizeof(logname), "%s-", log->logname); int len = strnlen(logname, sizeof(logname)); if (strncmp(logname, entry->d_name, len) != 0) { return 0; } /* get log file mtime */ snprintf(filename, sizeof(filename), "%s/%s", path, entry->d_name); if (stat(filename, &sb) != 0) { return -1; } if (oldestlog->mtime == 0 || oldestlog->mtime > sb.st_mtime) { oldestlog->mtime = sb.st_mtime; strncpy(oldestlog->name, entry->d_name, sizeof(oldestlog->name) - 1); oldestlog->name[sizeof(oldestlog->name) - 1] = '\0'; return 0; } return 0; } static int _tlog_remove_oldestlog(struct tlog_log *log) { struct oldest_log oldestlog; oldestlog.name[0] = 0; oldestlog.mtime = 0; oldestlog.log = log; /* get oldest log file name */ if (_tlog_list_dir(log->logdir, _tlog_get_oldest_callback, &oldestlog) != 0) { return -1; } char filename[PATH_MAX * 2]; snprintf(filename, sizeof(filename), "%s/%s", log->logdir, oldestlog.name); /* delete */ unlink(filename); return 0; } static int _tlog_remove_oldlog(struct tlog_log *log) { struct count_log count_log; int i = 0; count_log.lognum = 0; count_log.log = log; /* get total log file number */ if (_tlog_list_dir(log->logdir, _tlog_count_log_callback, &count_log) != 0) { fprintf(stderr, "tlog: get log file count failed.\n"); return -1; } /* remove last N log files */ for (i = 0; i < count_log.lognum - log->logcount; i++) { _tlog_remove_oldestlog(log); } return 0; } static void _tlog_log_unlock(struct tlog_log *log) { char lock_file[PATH_MAX * 2]; if (log->fd_lock <= 0) { return; } snprintf(lock_file, sizeof(lock_file), "%s/%s.lock", log->logdir, log->logname); unlink(lock_file); close(log->fd_lock); log->fd_lock = -1; } static int _tlog_log_lock(struct tlog_log *log) { char lock_file[PATH_MAX * 2]; int fd; if (log->multi_log == 0) { return 0; } snprintf(lock_file, sizeof(lock_file), "%s/%s.lock", log->logdir, log->logname); fd = open(lock_file, O_RDWR | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) { fprintf(stderr, "tlog: create lock file failed, %s", strerror(errno)); return -1; } if (lockf(fd, F_TLOCK, 0) < 0) { goto errout; } log->fd_lock = fd; return 0; errout: if (fd > 0) { close(fd); } return -1; } static void _tlog_wait_pid(struct tlog_log *log, int wait_hang) { int status; if (log->zip_pid <= 0) { return; } int option = (wait_hang == 0) ? WNOHANG : 0; /* check and obtain gzip process status*/ if (waitpid(log->zip_pid, &status, option) <= 0) { if (errno != ECHILD || errno == EINTR) { return; } } /* gzip process exited */ log->zip_pid = -1; char gzip_file[PATH_MAX * 2]; /* rename zipped file */ snprintf(gzip_file, sizeof(gzip_file), "%s/%s.pending.gz", log->logdir, log->logname); if (_tlog_rename_logfile(log, gzip_file) != 0) { _tlog_log_unlock(log); return; } /* remove oldest file */ _tlog_remove_oldlog(log); _tlog_log_unlock(log); } static void _tlog_close_all_fd_by_res(void) { struct rlimit lim; int maxfd = 0; int i = 0; getrlimit(RLIMIT_NOFILE, &lim); maxfd = lim.rlim_cur; if (maxfd > 4096) { maxfd = 4096; } for (i = 3; i < maxfd; i++) { close(i); } } static int _tlog_str_to_int(const char *str) { int num = 0; while (*str >= '0' && *str <= '9') { num = num * 10 + (*str - '0'); ++str; } if (*str) { return -1; } return num; } static void _tlog_close_all_fd(void) { #if defined(__linux__) int dir_fd = -1; dir_fd = open("/proc/self/fd/", O_RDONLY | O_DIRECTORY); if (dir_fd < 0) { goto errout; } char buffer[sizeof(struct linux_dirent64)]; int bytes; while ((bytes = syscall(SYS_getdents64, dir_fd, (struct linux_dirent64 *)buffer, sizeof(buffer))) > 0) { struct linux_dirent64 *entry; int offset; for (offset = 0; offset < bytes; offset += entry->d_reclen) { int fd; entry = (struct linux_dirent64 *)(buffer + offset); if ((fd = _tlog_str_to_int(entry->d_name)) < 0) { continue; } if (fd == dir_fd || fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) { continue; } close(fd); } } if (bytes < 0) { goto errout; } close(dir_fd); return; errout: if (dir_fd > 0) { close(dir_fd); dir_fd = -1; } #endif _tlog_close_all_fd_by_res(); return; } static int _tlog_archive_log_compressed(struct tlog_log *log) { char gzip_file[TLOG_BUFF_LEN]; char log_file[TLOG_BUFF_LEN]; char pending_file[TLOG_BUFF_LEN]; snprintf(gzip_file, sizeof(gzip_file), "%s/%s.pending.gz", log->logdir, log->logname); snprintf(pending_file, sizeof(pending_file), "%s/%s.pending", log->logdir, log->logname); if (_tlog_log_lock(log) != 0) { return -1; } /* if pending.zip exists */ if (access(gzip_file, F_OK) == 0) { /* rename it to standard name */ if (_tlog_rename_logfile(log, gzip_file) != 0) { goto errout; } } if (access(pending_file, F_OK) != 0) { /* rename current log file to pending */ snprintf(log_file, sizeof(log_file), "%s/%s", log->logdir, log->logname); if (rename(log_file, pending_file) != 0) { goto errout; } } /* start gzip process to compress log file */ if (log->zip_pid <= 0) { // NOLINTNEXTLINE(bugprone-unsafe-functions): vfork is safe here as we immediately exec int pid = vfork(); if (pid == 0) { _tlog_close_all_fd(); execl(tlog.gzip_cmd, tlog.gzip_cmd, "-1", pending_file, NULL); fprintf(stderr, "tlog: execl gzip failed, no compress\n"); log->nocompress = 1; _exit(1); } else if (pid < 0) { if (errno == EPERM || errno == EACCES) { fprintf(stderr, "tlog: vfork failed, errno: %d, no compress\n", errno); log->nocompress = 1; } goto errout; } log->zip_pid = pid; } return 0; errout: _tlog_log_unlock(log); return -1; } static int _tlog_archive_log_nocompress(struct tlog_log *log) { char log_file[TLOG_BUFF_LEN]; char pending_file[TLOG_BUFF_LEN]; snprintf(pending_file, sizeof(pending_file), "%s/%s.pending", log->logdir, log->logname); if (_tlog_log_lock(log) != 0) { return -1; } if (access(pending_file, F_OK) != 0) { /* rename current log file to pending */ snprintf(log_file, sizeof(log_file), "%s/%s", log->logdir, log->logname); if (rename(log_file, pending_file) != 0) { goto errout; } } /* rename pending file */ if (_tlog_rename_logfile(log, pending_file) != 0) { goto errout; } /* remove oldest file */ _tlog_remove_oldlog(log); _tlog_log_unlock(log); return 0; errout: _tlog_log_unlock(log); return -1; } static int _tlog_archive_log(struct tlog_log *log) { if (log->nocompress) { return _tlog_archive_log_nocompress(log); } else { return _tlog_archive_log_compressed(log); } } static void _tlog_get_log_name_dir(struct tlog_log *log) { char log_file[PATH_MAX + 1]; if (log->fd > 0) { close(log->fd); log->fd = -1; } pthread_mutex_lock(&tlog.lock); strncpy(log_file, log->pending_logfile, sizeof(log_file) - 1); log_file[sizeof(log_file) - 1] = '\0'; strncpy(log->logdir, dirname(log_file), sizeof(log->logdir) - 1); log->logdir[sizeof(log->logdir) - 1] = '\0'; strncpy(log_file, log->pending_logfile, PATH_MAX); log_file[sizeof(log_file) - 1] = '\0'; strncpy(log->logname, basename(log_file), sizeof(log->logname) - 1); log->logname[sizeof(log->logname) - 1] = '\0'; snprintf(log->logfile, sizeof(log->logfile), "%s/%s", log->logdir, log->logname); pthread_mutex_unlock(&tlog.lock); } static int _tlog_write(struct tlog_log *log, const char *buff, int bufflen) { int len; int unused __attribute__((unused)); struct stat sb = { 0 }; if (bufflen <= 0 || log->fail) { return 0; } if (log->rename_pending) { _tlog_get_log_name_dir(log); log->rename_pending = 0; } if (log->logcount <= 0 || log->logsize <= 0) { return 0; } /* if log file size exceeds threshold, start to compress */ if (log->multi_log && log->fd > 0) { log->filesize = lseek(log->fd, 0, SEEK_END); } if (log->filesize > log->logsize && log->zip_pid <= 0) { if (log->filesize < lseek(log->fd, 0, SEEK_END) && log->multi_log == 0) { const char *msg = "[Auto enable multi-process write mode, log may be lost, please enable multi-process write mode manually]\n"; log->multi_log = 1; unused = write(log->fd, msg, strlen(msg)); } close(log->fd); log->fd = -1; log->filesize = 0; _tlog_archive_log(log); } if ((log->fd <= 0 && log->logsize > 0) || ((0 == fstat(log->fd, &sb)) && (0 == sb.st_nlink)) // log file was deleted ) { /* open a new log file to write */ time_t now; if (log->fd > 0) { close(log->fd); log->fd = -1; } time(&now); if (now == log->last_try) { return -1; } log->last_try = now; if (_tlog_mkdir(log->logdir) != 0) { if (log->print_errmsg == 0) { return -1; } log->print_errmsg = 0; fprintf(stderr, "tlog: create log dir %s failed, %s\n", log->logdir, strerror(errno)); if (errno == EACCES && log->logscreen == 0) { fprintf(stderr, "tlog: no permission to write log file, output log to console\n"); tlog_logscreen(log, 1); tlog_logcount(log, 0); } return -1; } log->filesize = 0; log->fd = open(log->logfile, O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, log->file_perm); if (log->fd < 0) { if (log->print_errmsg == 0) { return -1; } fprintf(stderr, "tlog: open log file %s failed, %s\n", log->logfile, strerror(errno)); log->print_errmsg = 0; return -1; } if (log->mode_changed != 0) { fchmod(log->fd, log->file_perm); } log->last_try = 0; log->print_errmsg = 1; /* get log file size */ log->filesize = lseek(log->fd, 0, SEEK_END); } /* write log to file */ len = write(log->fd, buff, bufflen); if (len > 0) { log->filesize += len; } else { if (log->fd > 0 && errno == ENOSPC) { close(log->fd); log->fd = -1; } } return len; } int tlog_write(struct tlog_log *log, const char *buff, int bufflen) { return _tlog_write(log, buff, bufflen); } static int _tlog_has_data(struct tlog_log *log) { if (log->end != log->start || log->ext_end > 0) { return 1; } return 0; } static int _tlog_any_has_data_locked(void) { struct tlog_log *next = NULL; next = tlog.log; while (next) { if (_tlog_has_data(next) == 1) { return 1; } next = next->next; } return 0; } static int _tlog_any_has_data(void) { int ret = 0; pthread_mutex_lock(&tlog.lock); ret = _tlog_any_has_data_locked(); pthread_mutex_unlock(&tlog.lock); return ret; } static int _tlog_wait_pids(void) { time_t now = time(NULL); struct tlog_log *next = NULL; static struct tlog_log *last_log = NULL; pthread_mutex_lock(&tlog.lock); for (next = tlog.log; next != NULL; next = next->next) { if (next->zip_pid <= 0) { continue; } if (next == last_log) { continue; } if (next->last_waitpid == now) { continue; } last_log = next; next->last_waitpid = now; pthread_mutex_unlock(&tlog.lock); _tlog_wait_pid(next, 0); return 0; } last_log = NULL; pthread_mutex_unlock(&tlog.lock); return 0; } static int _tlog_close(struct tlog_log *log, int wait_hang) { struct tlog_log *next = tlog.log; if (log == NULL) { return -1; } if (log->zip_pid > 0) { _tlog_wait_pid(log, wait_hang); if (log->zip_pid > 0) { return -1; } } if (log->fd > 0) { close(log->fd); log->fd = -1; } _tlog_log_unlock(log); if (log->buff != NULL) { free(log->buff); log->buff = NULL; } if (next == log) { tlog.log = next->next; free(log); return 0; } while (next) { if (next->next == log) { next->next = log->next; free(log); return -1; } next = next->next; } pthread_cond_destroy(&log->client_cond); pthread_mutex_destroy(&log->lock); return 0; } static struct tlog_log *_tlog_next_log(struct tlog_log *last_log) { if (last_log == NULL) { return tlog.log; } return last_log->next; } static struct tlog_log *_tlog_wait_log_locked(struct tlog_log *last_log) { int ret = 0; struct timespec tm; struct tlog_log *log = NULL; struct tlog_log *next = NULL; int need_wait_pid = 0; for (next = tlog.log; next != NULL; next = next->next) { if (next->zip_pid > 0) { need_wait_pid = 1; break; } } clock_gettime(CLOCK_REALTIME, &tm); tm.tv_sec += 2; tlog.is_wait = 1; tlog.wait_on_log = last_log; if (need_wait_pid != 0) { ret = pthread_cond_timedwait(&tlog.cond, &tlog.lock, &tm); } else { ret = pthread_cond_wait(&tlog.cond, &tlog.lock); } tlog.is_wait = 0; tlog.wait_on_log = NULL; errno = ret; if (ret == 0 || ret == ETIMEDOUT) { log = tlog.notify_log; tlog.notify_log = NULL; } return log; } static void _tlog_wakeup_waiters(struct tlog_log *log) { pthread_mutex_lock(&log->lock); if (log->waiters > 0) { /* if there are waiters, wakeup */ pthread_cond_broadcast(&log->client_cond); } pthread_mutex_unlock(&log->lock); } static void _tlog_write_one_segment_log(struct tlog_log *log, char *buff, int bufflen) { struct tlog_segment_head *segment_head = NULL; int write_len = 0; segment_head = (struct tlog_segment_head *)buff; for (write_len = 0; write_len < bufflen;) { if (segment_head->magic != TLOG_SEGMENT_MAGIC) { return; } _tlog_write_output_func(log, segment_head->data, segment_head->len - 1); write_len += segment_head->len + sizeof(*segment_head); segment_head = (struct tlog_segment_head *)(buff + write_len); } } static void _tlog_write_segments_log(struct tlog_log *log, int log_len, int log_extlen) { _tlog_write_one_segment_log(log, log->buff + log->start, log_len); if (log_extlen > 0) { /* write extend buffer log */ _tlog_write_one_segment_log(log, log->buff, log_extlen); } } static void _tlog_write_buff_log(struct tlog_log *log, int log_len, int log_extlen) { _tlog_write_output_func(log, log->buff + log->start, log_len); if (log_extlen > 0) { /* write extend buffer log */ _tlog_write_output_func(log, log->buff, log_extlen); } } static void _tlog_work_write(struct tlog_log *log, int log_len, int log_extlen, int log_dropped) { /* write log */ if (log->segment_log) { _tlog_write_segments_log(log, log_len, log_extlen); } else { _tlog_write_buff_log(log, log_len, log_extlen); } if (log_dropped > 0) { /* if there is dropped log, record dropped log number */ char dropmsg[TLOG_TMP_LEN]; char *msg = dropmsg; struct tlog_segment_log_head *log_head = NULL; if (log->segment_log) { memset(dropmsg, 0, sizeof(struct tlog_segment_log_head)); log_head = (struct tlog_segment_log_head *)dropmsg; msg += sizeof(struct tlog_segment_log_head); log_head->info.level = TLOG_WARN; } int len = snprintf(msg, msg - dropmsg, "[Total Dropped %d Messages]\n", log_dropped); if (log_head) { log_head->len = len; } _tlog_write_output_func(log, dropmsg, strnlen(dropmsg, sizeof(dropmsg))); } } static int _tlog_root_write_screen_log(struct tlog_log *log, struct tlog_loginfo *info, const char *buff, int bufflen) { if (log->logscreen == 0) { return 0; } return _tlog_write_screen(log, info, buff, bufflen); } static int _tlog_root_write_log(struct tlog_log *log, const char *buff, int bufflen) { struct tlog_segment_log_head *head = NULL; static struct tlog_segment_log_head empty_info = { .info.level = TLOG_INFO }; if (tlog.output_func == NULL) { if (log->segment_log) { head = (struct tlog_segment_log_head *)buff; _tlog_root_write_screen_log(log, &head->info, head->data, head->len); return _tlog_write(log, head->data, head->len); } _tlog_root_write_screen_log(log, NULL, buff, bufflen); return _tlog_write(log, buff, bufflen); } if (log->segment_log && tlog.root == log) { head = (struct tlog_segment_log_head *)buff; _tlog_root_write_screen_log(log, &head->info, head->data, head->len); return tlog.output_func(&head->info, head->data, head->len, tlog_get_private(log)); } _tlog_root_write_screen_log(log, NULL, buff, bufflen); return tlog.output_func(&empty_info.info, buff, bufflen, tlog_get_private(log)); } static void tlog_wait_zip_fini(void) { tlog_log *next; if (tlog.root == NULL) { return; } int wait_zip = 1; int time_out = 0; while (wait_zip) { wait_zip = 0; time_out++; next = tlog.log; while (next) { if (next->zip_pid > 0 && wait_zip == 0) { wait_zip = 1; usleep(1000); } if (kill(next->zip_pid, 0) != 0 || time_out >= 5000) { next->zip_pid = -1; } next = next->next; } } return; } static void *_tlog_work(void *arg) { int log_len = 0; int log_extlen = 0; int log_end = 0; int log_extend = 0; int log_dropped = 0; struct tlog_log *log = NULL; struct tlog_log *loop_log = NULL; void *unused __attribute__((unused)); unused = arg; // for child process tlog_wait_zip_fini(); while (1) { log_len = 0; log_extlen = 0; log_extend = 0; if (__atomic_load_n(&tlog.run, __ATOMIC_RELAXED) == 0) { if (_tlog_any_has_data() == 0) { break; } } _tlog_wait_pids(); pthread_mutex_lock(&tlog.lock); if (loop_log == NULL) { loop_log = log; } log = _tlog_next_log(log); if (log == NULL) { pthread_mutex_unlock(&tlog.lock); continue; } /* if buffer is empty, wait */ if (_tlog_any_has_data_locked() == 0 && __atomic_load_n(&tlog.run, __ATOMIC_RELAXED)) { log = _tlog_wait_log_locked(log); if (log == NULL) { pthread_mutex_unlock(&tlog.lock); if (errno != ETIMEDOUT && __atomic_load_n(&tlog.run, __ATOMIC_RELAXED)) { sleep(1); } continue; } } if (_tlog_has_data(log) == 0) { if (log->is_exit) { if (_tlog_close(log, 0) == 0) { log = NULL; loop_log = NULL; }; } pthread_mutex_unlock(&tlog.lock); continue; } loop_log = NULL; if (log->ext_end > 0) { log_len = log->ext_end - log->start; log_extend = log->ext_end; } if (log->end < log->start) { log_extlen = log->end; } else if (log->end > log->start) { log_len = log->end - log->start; } log_end = log->end; log_dropped = log->dropped; log->dropped = 0; pthread_mutex_unlock(&tlog.lock); /* start write log work */ _tlog_work_write(log, log_len, log_extlen, log_dropped); pthread_mutex_lock(&tlog.lock); /* release finished buffer */ log->start = log_end; if (log_extend > 0) { log->ext_end = 0; } pthread_mutex_unlock(&tlog.lock); _tlog_wakeup_waiters(log); } return NULL; } void tlog_set_early_printf(int enable, int no_prefix, int color) { tlog.early_print_disable = (enable == 0) ? 1 : 0; tlog.early_print_no_prefix = (no_prefix == 0) ? 0 : 1; tlog.early_print_color = (color == 0 || isatty(STDOUT_FILENO) == 0) ? 0 : 1; } void tlog_reg_early_printf_callback(tlog_early_print_func callback) { tlog.tlog_early_print = callback; } void tlog_reg_early_printf_output_callback(tlog_log_output_func callback, int log_screen, void *private_data) { tlog.early_print_output = callback; tlog.early_print_userptr = private_data; tlog.early_print_with_screen = (log_screen == 0) ? 0 : 1; } const char *tlog_get_level_string(tlog_level level) { if (level >= TLOG_END) { return NULL; } return tlog_level_str[level]; } void tlog_set_maxlog_count(int count) { tlog_logcount(tlog.root, count); } static void _tlog_log_setlogscreen(struct tlog_log *log, int enable) { if (log == NULL) { return; } log->logscreen = (enable != 0) ? 1 : 0; } void tlog_setlogscreen(int enable) { _tlog_log_setlogscreen(tlog.root, enable); } int tlog_write_log(const char *buff, int bufflen) { if (unlikely(tlog.root == NULL)) { return -1; } return _tlog_write(tlog.root, buff, bufflen); } void tlog_logscreen(tlog_log *log, int enable) { if (log == NULL) { return; } _tlog_log_setlogscreen(log, enable); } static int _tlog_reg_output_func(tlog_log *log, tlog_output_func output) { if (log == NULL) { return -1; } if (output == NULL) { log->output_func = _tlog_write; return 0; } log->output_func = output; return 0; } int tlog_reg_output_func(tlog_log *log, tlog_output_func output) { if (log == tlog.root) { return -1; } int ret = _tlog_reg_output_func(log, output); if (ret == 0) { log->set_custom_output_func = 1; } return ret; } int tlog_reg_format_func(tlog_format_func callback) { tlog.root_format = callback; return 0; } int tlog_reg_log_output_func(tlog_log_output_func output, void *private_data) { tlog.output_func = output; tlog_set_private(tlog.root, private_data); tlog.log->set_custom_output_func = 1; return 0; } int tlog_setlevel(tlog_level level) { if (level >= TLOG_END) { return -1; } tlog_set_level = level; return 0; } int tlog_log_enabled(tlog_level level) { if (level >= TLOG_END) { return 0; } return (tlog_set_level >= level) ? 1 : 0; } tlog_level tlog_getlevel(void) { return tlog_set_level; } void tlog_set_logfile(const char *logfile) { tlog_rename_logfile(tlog.root, logfile); } static void _tlog_get_gzip_cmd_path(void) { char *copy_path = NULL; char gzip_cmd_path[PATH_MAX]; const char *env_path = getenv("PATH"); char *save_ptr = NULL; if (env_path == NULL) { env_path = "/bin:/usr/bin:/usr/local/bin"; } copy_path = strdup(env_path); if (copy_path == NULL) { return; } for (char *tok = strtok_r(copy_path, ":", &save_ptr); tok; tok = strtok_r(NULL, ":", &save_ptr)) { snprintf(gzip_cmd_path, sizeof(gzip_cmd_path), "%s/gzip", tok); if (access(gzip_cmd_path, X_OK) != 0) { continue; } snprintf(tlog.gzip_cmd, sizeof(tlog.gzip_cmd), "%s", gzip_cmd_path); break; } free(copy_path); } tlog_log *tlog_open(const char *logfile, int maxlogsize, int maxlogcount, int buffsize, unsigned int flag) { struct tlog_log *log = NULL; if (__atomic_load_n(&tlog.run, __ATOMIC_RELAXED) == 0) { fprintf(stderr, "tlog: tlog is not initialized.\n"); return NULL; } log = (struct tlog_log *)calloc(1, sizeof(*log)); if (log == NULL) { fprintf(stderr, "tlog: malloc log failed.\n"); return NULL; } log->start = 0; log->end = 0; log->ext_end = 0; log->dropped = 0; log->buffsize = (buffsize > 0) ? buffsize : TLOG_BUFF_SIZE; log->logsize = (maxlogsize >= 0) ? maxlogsize : TLOG_LOG_SIZE; log->logcount = (maxlogcount <= 0) ? 0 : maxlogcount; log->fd = -1; log->filesize = 0; log->zip_pid = -1; log->is_exit = 0; log->fail = 0; log->print_errmsg = 1; log->waiters = 0; log->block = ((flag & TLOG_NONBLOCK) == 0) ? 1 : 0; log->nocompress = ((flag & TLOG_NOCOMPRESS) == 0) ? 0 : 1; log->logscreen = ((flag & TLOG_SCREEN) == 0) ? 0 : 1; log->logscreen_color = ((flag & TLOG_SCREEN_COLOR) == 0 || isatty(STDOUT_FILENO) == 0) ? 0 : 1; log->multi_log = ((flag & TLOG_MULTI_WRITE) == 0) ? 0 : 1; log->segment_log = ((flag & TLOG_SEGMENT) == 0) ? 0 : 1; log->max_line_size = TLOG_MAX_LINE_LEN; log->output_func = _tlog_write; log->file_perm = S_IRUSR | S_IWUSR | S_IRGRP; log->archive_perm = S_IRUSR | S_IRGRP; if (log->nocompress == 0 && tlog.gzip_cmd[0] == '\0') { log->nocompress = 1; } if (log->logscreen_color == 1) { log->logscreen = 1; log->segment_log = 1; } tlog_rename_logfile(log, logfile); if (log->nocompress) { strncpy(log->suffix, TLOG_SUFFIX_LOG, sizeof(log->suffix)); } else { strncpy(log->suffix, TLOG_SUFFIX_GZ, sizeof(log->suffix)); } log->buff = (char *)malloc(log->buffsize); if (log->buff == NULL) { fprintf(stderr, "tlog: malloc log buffer failed, %s\n", strerror(errno)); goto errout; } pthread_mutex_lock(&tlog.lock); if (tlog.log == NULL) { tlog.log = log; } else { log->next = tlog.log; tlog.log = log; } pthread_mutex_unlock(&tlog.lock); return log; errout: if (log) { pthread_cond_destroy(&log->client_cond); pthread_mutex_destroy(&log->lock); free(log); log = NULL; } return NULL; } void tlog_close(tlog_log *log) { if (log == NULL) { return; } log->is_exit = 1; } void tlog_rename_logfile(struct tlog_log *log, const char *logfile) { pthread_mutex_lock(&tlog.lock); strncpy(log->pending_logfile, logfile, sizeof(log->pending_logfile) - 1); pthread_mutex_unlock(&tlog.lock); log->rename_pending = 1; } static void tlog_fork_prepare(void) { if (tlog.root == NULL) { return; } pthread_mutex_lock(&tlog.lock); tlog_log *next; next = tlog.log; while (next) { next->multi_log = 1; next = next->next; } } static void tlog_fork_parent(void) { if (tlog.root == NULL) { return; } pthread_mutex_unlock(&tlog.lock); } static void tlog_fork_child(void) { pthread_attr_t attr; tlog_log *next; if (tlog.root == NULL) { return; } next = tlog.log; while (next) { next->start = 0; next->end = 0; next->ext_end = 0; next->dropped = 0; next->filesize = 0; next = next->next; } pthread_attr_init(&attr); int ret = pthread_create(&tlog.tid, &attr, _tlog_work, NULL); if (ret != 0) { fprintf(stderr, "tlog: create tlog work thread failed, %s\n", strerror(errno)); goto errout; } goto out; errout: next = tlog.log; while (next) { next->fail = 1; next = next->next; } out: pthread_mutex_unlock(&tlog.lock); } int tlog_init(const char *logfile, int maxlogsize, int maxlogcount, int buffsize, unsigned int flag) { pthread_attr_t attr; int ret; struct tlog_log *log = NULL; if (tlog.root_format != NULL) { fprintf(stderr, "tlog: already initialized.\n"); return -1; } if (buffsize > 0 && buffsize < TLOG_MAX_LINE_SIZE_SET * 2) { fprintf(stderr, "tlog: buffer size is invalid.\n"); return -1; } memset(&tlog, 0, sizeof(tlog)); tlog.is_wait = 0; _tlog_get_gzip_cmd_path(); pthread_attr_init(&attr); pthread_cond_init(&tlog.cond, NULL); pthread_mutex_init(&tlog.lock, NULL); __atomic_store_n(&tlog.run, 1, __ATOMIC_RELAXED); log = tlog_open(logfile, maxlogsize, maxlogcount, buffsize, flag); if (log == NULL) { fprintf(stderr, "tlog: init tlog root failed.\n"); goto errout; } _tlog_reg_output_func(log, _tlog_root_write_log); if ((flag & TLOG_NOCOMPRESS) == 0 && tlog.gzip_cmd[0] == '\0') { fprintf(stderr, "tlog: can not find gzip command, disable compress.\n"); } tlog.output_no_prefix = ((flag & TLOG_FORMAT_NO_PREFIX) == 0) ? 0 : 1; tlog.root = log; tlog.root_format = _tlog_root_default_format; ret = pthread_create(&tlog.tid, &attr, _tlog_work, NULL); if (ret != 0) { fprintf(stderr, "tlog: create tlog work thread failed, %s\n", strerror(errno)); goto errout; } if (flag & TLOG_SUPPORT_FORK) { pthread_atfork(&tlog_fork_prepare, &tlog_fork_parent, &tlog_fork_child); } return 0; errout: if (tlog.tid) { void *retval = NULL; __atomic_store_n(&tlog.run, 0, __ATOMIC_RELAXED); pthread_join(tlog.tid, &retval); tlog.tid = 0; } pthread_cond_destroy(&tlog.cond); pthread_mutex_destroy(&tlog.lock); __atomic_store_n(&tlog.run, 0, __ATOMIC_RELAXED); tlog.root = NULL; tlog.root_format = NULL; _tlog_close(log, 1); return -1; } void tlog_exit(void) { if (tlog.root_format == NULL) { return; } if (tlog.tid) { void *ret = NULL; __atomic_store_n(&tlog.run, 0, __ATOMIC_RELAXED); pthread_mutex_lock(&tlog.lock); pthread_cond_signal(&tlog.cond); pthread_mutex_unlock(&tlog.lock); pthread_join(tlog.tid, &ret); tlog.tid = 0; } tlog.root = NULL; while (tlog.log) { _tlog_close(tlog.log, 1); } pthread_cond_destroy(&tlog.cond); pthread_mutex_destroy(&tlog.lock); tlog.root_format = NULL; tlog.is_wait = 0; } ================================================ FILE: src/utils/alpn.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #include int encode_alpn_protos(const char *alpn, uint8_t *alpn_data, int alpn_data_max) { int alpn_data_len = 0; const char *alpn_str = alpn; if (alpn == NULL || alpn[0] == 0 || alpn_data == NULL || alpn_data_max <= 0) { return 0; } /* Parse comma-separated ALPN protocols and encode in wire format */ while (*alpn_str && alpn_data_len < alpn_data_max - 1) { const char *comma = strchr(alpn_str, ','); int proto_len; if (comma) { proto_len = comma - alpn_str; } else { proto_len = strnlen(alpn_str, alpn_data_max - alpn_data_len - 1); } /* Skip empty protocols */ if (proto_len == 0) { alpn_str = comma ? comma + 1 : alpn_str + proto_len; continue; } /* Check if we have space for length byte + protocol */ if (alpn_data_len + 1 + proto_len > alpn_data_max) { tlog(TLOG_WARN, "ALPN string too long, truncating."); break; } /* Write length-prefixed protocol */ alpn_data[alpn_data_len++] = (uint8_t)proto_len; memcpy(alpn_data + alpn_data_len, alpn_str, proto_len); alpn_data_len += proto_len; /* Move to next protocol */ alpn_str = comma ? comma + 1 : alpn_str + proto_len; if (!comma) { break; } } return alpn_data_len; } ================================================ FILE: src/utils/capbility.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/util.h" #include "smartdns/dns_conf.h" #include #include #include #include #include #include int get_uid_gid(uid_t *uid, gid_t *gid) { struct passwd *result = NULL; struct passwd pwd; char *buf = NULL; ssize_t bufsize = 0; int ret = -1; if (dns_conf.user[0] == '\0') { *uid = getuid(); *gid = getgid(); return 0; } bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); if (bufsize == -1) { bufsize = 1024 * 16; } buf = malloc(bufsize); if (buf == NULL) { goto out; } ret = getpwnam_r(dns_conf.user, &pwd, buf, bufsize, &result); if (ret != 0) { goto out; } if (result == NULL) { ret = -1; goto out; } *uid = result->pw_uid; *gid = result->pw_gid; out: if (buf) { free(buf); } return ret; } int capget(struct __user_cap_header_struct *header, struct __user_cap_data_struct *cap); int capset(struct __user_cap_header_struct *header, struct __user_cap_data_struct *cap); int drop_root_privilege(void) { struct __user_cap_data_struct cap[2]; struct __user_cap_header_struct header; #ifdef _LINUX_CAPABILITY_VERSION_3 header.version = _LINUX_CAPABILITY_VERSION_3; #else header.version = _LINUX_CAPABILITY_VERSION; #endif header.pid = 0; uid_t uid = 0; gid_t gid = 0; int unused __attribute__((unused)) = 0; if (get_uid_gid(&uid, &gid) != 0) { return -1; } memset(cap, 0, sizeof(cap)); if (capget(&header, cap) < 0) { return -1; } prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); for (int i = 0; i < 2; i++) { cap[i].effective = (1 << CAP_NET_RAW | 1 << CAP_NET_ADMIN | 1 << CAP_NET_BIND_SERVICE); cap[i].permitted = (1 << CAP_NET_RAW | 1 << CAP_NET_ADMIN | 1 << CAP_NET_BIND_SERVICE); } unused = setgid(gid); unused = setuid(uid); if (capset(&header, cap) < 0) { return -1; } prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0); return 0; } ================================================ FILE: src/utils/daemon.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/util.h" #include #include #include #include #include #include #include #include #include #include enum daemon_msg_type { DAEMON_MSG_KICKOFF, DAEMON_MSG_KEEPALIVE, DAEMON_MSG_DAEMON_PID, }; struct daemon_msg { enum daemon_msg_type type; int value; }; static int pidfile_fd; static int daemon_fd; static void _close_all_fd_by_res(void) { struct rlimit lim; int maxfd = 0; int i = 0; getrlimit(RLIMIT_NOFILE, &lim); maxfd = lim.rlim_cur; if (maxfd > 4096) { maxfd = 4096; } for (i = 3; i < maxfd; i++) { close(i); } } void close_all_fd(int keepfd) { DIR *dirp; int dir_fd = -1; struct dirent *dentp; dirp = opendir("/proc/self/fd"); if (dirp == NULL) { goto errout; } dir_fd = dirfd(dirp); while ((dentp = readdir(dirp)) != NULL) { int fd = atol(dentp->d_name); if (fd < 0) { continue; } if (fd == dir_fd || fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO || fd == keepfd) { continue; } close(fd); } closedir(dirp); return; errout: if (dirp) { closedir(dirp); } _close_all_fd_by_res(); return; } void daemon_close_stdfds(void) { int fd_null = open("/dev/null", O_RDWR); if (fd_null < 0) { fprintf(stderr, "open /dev/null failed, %s\n", strerror(errno)); return; } dup2(fd_null, STDIN_FILENO); dup2(fd_null, STDOUT_FILENO); dup2(fd_null, STDERR_FILENO); if (fd_null > 2) { close(fd_null); } } int daemon_kickoff(int status, int no_close) { struct daemon_msg msg; if (daemon_fd <= 0) { return -1; } msg.type = DAEMON_MSG_KICKOFF; msg.value = status; int ret = write(daemon_fd, &msg, sizeof(msg)); if (ret != sizeof(msg)) { fprintf(stderr, "notify parent process failed, %s\n", strerror(errno)); return -1; } if (no_close == 0) { daemon_close_stdfds(); } close(daemon_fd); daemon_fd = -1; return 0; } int daemon_keepalive(void) { struct daemon_msg msg; static time_t last = 0; time_t now = time(NULL); if (daemon_fd <= 0) { return -1; } if (now == last) { return 0; } last = now; msg.type = DAEMON_MSG_KEEPALIVE; msg.value = 0; int ret = write(daemon_fd, &msg, sizeof(msg)); if (ret != sizeof(msg)) { return -1; } return 0; } daemon_ret daemon_run(int *wstatus) { pid_t pid = 0; int fds[2] = {0}; if (pipe(fds) != 0) { fprintf(stderr, "run daemon process failed, pipe failed, %s\n", strerror(errno)); return -1; } pid = fork(); if (pid < 0) { fprintf(stderr, "run daemon process failed, fork failed, %s\n", strerror(errno)); close(fds[0]); close(fds[1]); return -1; } else if (pid > 0) { struct pollfd pfd; int ret = 0; close(fds[1]); pfd.fd = fds[0]; pfd.events = POLLIN; pfd.revents = 0; do { ret = poll(&pfd, 1, 3000); if (ret <= 0) { fprintf(stderr, "run daemon process failed, wait child timeout, kill child.\n"); goto errout; } if (!(pfd.revents & POLLIN)) { goto errout; } struct daemon_msg msg; ret = read(fds[0], &msg, sizeof(msg)); if (ret != sizeof(msg)) { goto errout; } if (msg.type == DAEMON_MSG_KEEPALIVE) { continue; } else if (msg.type == DAEMON_MSG_DAEMON_PID) { pid = msg.value; continue; } else if (msg.type == DAEMON_MSG_KICKOFF) { if (wstatus != NULL) { *wstatus = msg.value; } return DAEMON_RET_PARENT_OK; } else { goto errout; } } while (1); return DAEMON_RET_ERR; } setsid(); pid = fork(); if (pid < 0) { fprintf(stderr, "double fork failed, %s\n", strerror(errno)); _exit(1); } else if (pid > 0) { struct daemon_msg msg; int unused __attribute__((unused)); msg.type = DAEMON_MSG_DAEMON_PID; msg.value = pid; unused = write(fds[1], &msg, sizeof(msg)); _exit(0); } umask(0); if (chdir("/") != 0) { goto errout; } close(fds[0]); daemon_fd = fds[1]; return DAEMON_RET_CHILD_OK; errout: kill(pid, SIGKILL); if (wstatus != NULL) { *wstatus = -1; } return DAEMON_RET_ERR; } int create_pid_file(const char *pid_file) { int fd = 0; int flags = 0; char buff[TMP_BUFF_LEN_32]; int unused __attribute__((unused)); /* create pid file, and lock this file */ fd = open(pid_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd == -1) { fprintf(stderr, "create pid file %s failed, %s\n", pid_file, strerror(errno)); return -1; } flags = fcntl(fd, F_GETFD); if (flags < 0) { fprintf(stderr, "Could not get flags for PID file %s\n", pid_file); goto errout; } flags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) == -1) { fprintf(stderr, "Could not set flags for PID file %s\n", pid_file); goto errout; } if (lockf(fd, F_TLOCK, 0) < 0) { memset(buff, 0, TMP_BUFF_LEN_32); if (read(fd, buff, TMP_BUFF_LEN_32) <= 0) { buff[0] = '\0'; } fprintf(stderr, "Server is already running, pid is %s", buff); goto errout; } unused = ftruncate(fd, 0); snprintf(buff, TMP_BUFF_LEN_32, "%d\n", getpid()); if (write(fd, buff, strnlen(buff, TMP_BUFF_LEN_32)) < 0) { fprintf(stderr, "write pid to file failed, %s.\n", strerror(errno)); goto errout; } if (pidfile_fd > 0) { close(pidfile_fd); } pidfile_fd = fd; return 0; errout: if (fd > 0) { close(fd); } return -1; } ================================================ FILE: src/utils/dns_debug.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/dns.h" #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #include #include #include #include #include #include #include #define BUFF_SZ 1024 #define PACKET_BUF_SIZE 8192 #define PACKET_MAGIC 0X11040918 int write_file(const char *filename, void *data, int data_len) { int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0) { return -1; } int len = write(fd, data, data_len); if (len < 0) { goto errout; } close(fd); return 0; errout: if (fd > 0) { close(fd); } return -1; } int dns_packet_save(const char *dir, const char *type, const char *from, const void *packet, int packet_len) { char *data = NULL; int data_len = 0; char filename[BUFF_SZ]; char time_s[BUFF_SZ]; int ret = -1; struct tm *ptm; struct tm tm; struct timeval tm_val; struct stat sb; if (stat(dir, &sb) != 0) { mkdir(dir, 0750); } if (gettimeofday(&tm_val, NULL) != 0) { return -1; } ptm = localtime_r(&tm_val.tv_sec, &tm); if (ptm == NULL) { return -1; } snprintf(time_s, sizeof(time_s) - 1, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d.%.3d", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, (int)(tm_val.tv_usec / 1000)); snprintf(filename, sizeof(filename) - 1, "%s/%s-%.4d%.2d%.2d-%.2d%.2d%.2d%.3d.packet", dir, type, ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, (int)(tm_val.tv_usec / 1000)); data = malloc(PACKET_BUF_SIZE); if (data == NULL) { return -1; } data_len = snprintf(data, PACKET_BUF_SIZE, "type: %s\n" "from: %s\n" "time: %s\n" "packet-len: %d\n", type, from, time_s, packet_len); if (data_len <= 0 || data_len >= PACKET_BUF_SIZE) { goto out; } data[data_len] = 0; data_len++; uint32_t magic = htonl(PACKET_MAGIC); memcpy(data + data_len, &magic, sizeof(magic)); data_len += sizeof(magic); int len_in_h = htonl(packet_len); memcpy(data + data_len, &len_in_h, sizeof(len_in_h)); data_len += 4; memcpy(data + data_len, packet, packet_len); data_len += packet_len; ret = write_file(filename, data, data_len); if (ret != 0) { goto out; } ret = 0; out: if (data) { free(data); } return ret; } #if defined(DEBUG) || defined(TEST) struct _dns_read_packet_info { int data_len; int message_len; char *message; int packet_len; uint8_t *packet; uint8_t data[0]; }; static struct _dns_read_packet_info *_dns_read_packet_file(const char *packet_file) { struct _dns_read_packet_info *info = NULL; int fd = -1; int len = 0; int message_len = 0; uint8_t *ptr = NULL; info = malloc(sizeof(struct _dns_read_packet_info) + PACKET_BUF_SIZE); fd = open(packet_file, O_RDONLY); if (fd < 0) { printf("open file %s failed, %s\n", packet_file, strerror(errno)); goto errout; } len = read(fd, info->data, PACKET_BUF_SIZE); if (len < 0) { printf("read file %s failed, %s\n", packet_file, strerror(errno)); goto errout; } message_len = strnlen((char *)info->data, PACKET_BUF_SIZE); if (message_len >= 512 || message_len >= len) { printf("invalid packet file, bad message len\n"); goto errout; } info->message_len = message_len; info->message = (char *)info->data; ptr = info->data + message_len + 1; uint32_t magic = 0; if (ptr - (uint8_t *)info + sizeof(magic) >= (size_t)len) { printf("invalid packet file, magic length is invalid.\n"); goto errout; } memcpy(&magic, ptr, sizeof(magic)); if (magic != htonl(PACKET_MAGIC)) { printf("invalid packet file, bad magic\n"); goto errout; } ptr += sizeof(magic); uint32_t packet_len = 0; if (ptr - info->data + sizeof(packet_len) >= (size_t)len) { printf("invalid packet file, packet length is invalid.\n"); goto errout; } memcpy(&packet_len, ptr, sizeof(packet_len)); packet_len = ntohl(packet_len); ptr += sizeof(packet_len); if (packet_len != (size_t)len - (ptr - info->data)) { printf("invalid packet file, packet length is invalid\n"); goto errout; } info->packet_len = packet_len; info->packet = ptr; close(fd); return info; errout: if (fd > 0) { close(fd); } if (info) { free(info); } return NULL; } static int _dns_debug_display(struct dns_packet *packet) { int i = 0; int j = 0; int ttl = 0; struct dns_rrs *rrs = NULL; int rr_count = 0; char req_host[MAX_IP_LEN]; int ret; for (j = 1; j < DNS_RRS_OPT; j++) { rrs = dns_get_rrs_start(packet, j, &rr_count); printf("section: %d\n", j); for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { switch (rrs->type) { case DNS_T_A: { unsigned char addr[4]; char name[DNS_MAX_CNAME_LEN] = {0}; /* get A result */ dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr); req_host[0] = '\0'; inet_ntop(AF_INET, addr, req_host, sizeof(req_host)); printf("domain: %s A: %s TTL: %d\n", name, req_host, ttl); } break; case DNS_T_AAAA: { unsigned char addr[16]; char name[DNS_MAX_CNAME_LEN] = {0}; dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr); req_host[0] = '\0'; inet_ntop(AF_INET6, addr, req_host, sizeof(req_host)); printf("domain: %s AAAA: %s TTL:%d\n", name, req_host, ttl); } break; case DNS_T_SRV: { unsigned short priority = 0; unsigned short weight = 0; unsigned short port = 0; char name[DNS_MAX_CNAME_LEN] = {0}; char target[DNS_MAX_CNAME_LEN]; ret = dns_get_SRV(rrs, name, DNS_MAX_CNAME_LEN, &ttl, &priority, &weight, &port, target, DNS_MAX_CNAME_LEN); if (ret < 0) { tlog(TLOG_DEBUG, "decode SRV failed, %s", name); return -1; } printf("domain: %s SRV: %s TTL: %d priority: %d weight: %d port: %d\n", name, target, ttl, priority, weight, port); } break; case DNS_T_SVCB: case DNS_T_HTTPS: { char name[DNS_MAX_CNAME_LEN] = {0}; char target[DNS_MAX_CNAME_LEN] = {0}; struct dns_svcparam *p = NULL; int priority = 0; ret = dns_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN); if (ret != 0) { printf("get HTTPS svcparm failed\n"); break; } printf("domain: %s HTTPS: %s TTL: %d priority: %d\n", name, target, ttl, priority); for (; p; p = dns_svcparm_next(rrs, p)) { switch (p->key) { case DNS_HTTPS_T_MANDATORY: { printf(" HTTPS: mandatory: %s\n", p->value); } break; case DNS_HTTPS_T_ALPN: { char alph[64] = {0}; int total_alph_len = 0; char *ptr = (char *)p->value; do { int alphlen = *ptr; memcpy(alph + total_alph_len, ptr + 1, alphlen); total_alph_len += alphlen; ptr += alphlen + 1; alph[total_alph_len] = ','; total_alph_len++; alph[total_alph_len] = ' '; total_alph_len++; } while (ptr - (char *)p->value < p->len); if (total_alph_len > 2) { alph[total_alph_len - 2] = '\0'; } printf(" HTTPS: alpn: %s\n", alph); } break; case DNS_HTTPS_T_NO_DEFAULT_ALPN: { printf(" HTTPS: no_default_alpn: %s\n", p->value); } break; case DNS_HTTPS_T_PORT: { int port = *(unsigned short *)(p->value); printf(" HTTPS: port: %d\n", port); } break; case DNS_HTTPS_T_IPV4HINT: { printf(" HTTPS: ipv4hint: %d\n", p->len / 4); for (int k = 0; k < p->len / 4; k++) { char ip[16] = {0}; inet_ntop(AF_INET, p->value + k * 4, ip, sizeof(ip)); printf(" ipv4: %s\n", ip); } } break; case DNS_HTTPS_T_ECH: { printf(" HTTPS: ech: "); for (int k = 0; k < p->len; k++) { printf("%02x ", p->value[k]); } printf("\n"); } break; case DNS_HTTPS_T_IPV6HINT: { printf(" HTTPS: ipv6hint: %d\n", p->len / 16); for (int k = 0; k < p->len / 16; k++) { char ip[64] = {0}; inet_ntop(AF_INET6, p->value + k * 16, ip, sizeof(ip)); printf(" ipv6: %s\n", ip); } } break; } } } break; case DNS_T_NS: { char cname[DNS_MAX_CNAME_LEN]; char name[DNS_MAX_CNAME_LEN] = {0}; dns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN); printf("domain: %s TTL: %d NS: %s\n", name, ttl, cname); } break; case DNS_T_CNAME: { char cname[DNS_MAX_CNAME_LEN]; char name[DNS_MAX_CNAME_LEN] = {0}; dns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN); printf("domain: %s TTL: %d CNAME: %s\n", name, ttl, cname); } break; case DNS_T_SOA: { char name[DNS_MAX_CNAME_LEN] = {0}; struct dns_soa soa; dns_get_SOA(rrs, name, 128, &ttl, &soa); printf("domain: %s SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, expire: " "%d, minimum: %d", name, soa.mname, soa.rname, soa.serial, soa.refresh, soa.retry, soa.expire, soa.minimum); } break; default: break; } } printf("\n"); } rr_count = 0; rrs = dns_get_rrs_start(packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return 0; } printf("section opt:\n"); for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { switch (rrs->type) { case DNS_OPT_T_TCP_KEEPALIVE: { unsigned short idle_timeout = 0; ret = dns_get_OPT_TCP_KEEPALIVE(rrs, &idle_timeout); if (idle_timeout == 0) { continue; } printf("tcp keepalive: %d\n", idle_timeout); } break; case DNS_OPT_T_ECS: { struct dns_opt_ecs ecs; memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } printf("ecs family: %d, src_prefix: %d, scope_prefix: %d, ", ecs.family, ecs.source_prefix, ecs.scope_prefix); if (ecs.family == 1) { char ip[16] = {0}; inet_ntop(AF_INET, ecs.addr, ip, sizeof(ip)); printf("ecs address: %s\n", ip); } else if (ecs.family == 2) { char ip[64] = {0}; inet_ntop(AF_INET6, ecs.addr, ip, sizeof(ip)); printf("ecs address: %s\n", ip); } } break; default: break; } } return 0; } int dns_packet_debug(const char *packet_file) { struct _dns_read_packet_info *info = NULL; char buff[DNS_PACKSIZE]; tlog_set_maxlog_count(0); tlog_setlogscreen(1); tlog_setlevel(TLOG_DEBUG); info = _dns_read_packet_file(packet_file); if (info == NULL) { goto errout; } const char *send_env = getenv("SMARTDNS_DEBUG_SEND"); if (send_env != NULL) { char ip[32]; int port = 53; if (parse_ip(send_env, ip, &port) == 0) { int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd > 0) { struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(port); server.sin_addr.s_addr = inet_addr(ip); sendto(sockfd, info->packet, info->packet_len, 0, (struct sockaddr *)&server, sizeof(server)); close(sockfd); } } } struct dns_packet *packet = (struct dns_packet *)buff; if (dns_decode(packet, DNS_PACKSIZE, info->packet, info->packet_len) != 0) { printf("decode failed.\n"); goto errout; } _dns_debug_display(packet); free(info); return 0; errout: if (info) { free(info); } return -1; } #endif ================================================ FILE: src/utils/ipset.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/util.h" #include #include #include #define NFNL_SUBSYS_IPSET 6 #define IPSET_ATTR_DATA 7 #define IPSET_ATTR_IP 1 #define IPSET_ATTR_IPADDR_IPV4 1 #define IPSET_ATTR_IPADDR_IPV6 2 #define IPSET_ATTR_PROTOCOL 1 #define IPSET_ATTR_SETNAME 2 #define IPSET_ATTR_TIMEOUT 6 #define IPSET_ADD 9 #define IPSET_DEL 10 #define IPSET_MAXNAMELEN 32 #define IPSET_PROTOCOL 6 #ifndef NFNETLINK_V0 #define NFNETLINK_V0 0 #endif #ifndef NLA_F_NESTED #define NLA_F_NESTED (1 << 15) #endif #ifndef NLA_F_NET_BYTEORDER #define NLA_F_NET_BYTEORDER (1 << 14) #endif #define NETLINK_ALIGN(len) (((len) + 3) & ~(3)) #define BUFF_SZ 1024 struct ipset_netlink_attr { unsigned short len; unsigned short type; }; struct ipset_netlink_msg { unsigned char family; unsigned char version; __be16 res_id; }; static int ipset_fd; static inline void _ipset_add_attr(struct nlmsghdr *netlink_head, uint16_t type, size_t len, const void *data) { struct ipset_netlink_attr *attr = (void *)netlink_head + NETLINK_ALIGN(netlink_head->nlmsg_len); uint16_t payload_len = NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)) + len; attr->type = type; attr->len = payload_len; memcpy((void *)attr + NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)), data, len); netlink_head->nlmsg_len += NETLINK_ALIGN(payload_len); } static int _ipset_socket_init(void) { if (ipset_fd > 0) { return 0; } ipset_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER); if (ipset_fd < 0) { return -1; } return 0; } static int _ipset_operate(const char *ipset_name, const unsigned char addr[], int addr_len, unsigned long timeout, int operate) { struct nlmsghdr *netlink_head = NULL; struct ipset_netlink_msg *netlink_msg = NULL; struct ipset_netlink_attr *nested[3]; char buffer[BUFF_SZ]; uint8_t proto = 0; ssize_t rc = 0; int af = 0; static const struct sockaddr_nl snl = {.nl_family = AF_NETLINK}; uint32_t expire = 0; if (addr_len != IPV4_ADDR_LEN && addr_len != IPV6_ADDR_LEN) { errno = EINVAL; return -1; } if (addr_len == IPV4_ADDR_LEN) { af = AF_INET; } else if (addr_len == IPV6_ADDR_LEN) { af = AF_INET6; } else { errno = EINVAL; return -1; } if (_ipset_socket_init() != 0) { return -1; } if (strlen(ipset_name) >= IPSET_MAXNAMELEN) { errno = ENAMETOOLONG; return -1; } memset(buffer, 0, BUFF_SZ); netlink_head = (struct nlmsghdr *)buffer; netlink_head->nlmsg_len = NETLINK_ALIGN(sizeof(struct nlmsghdr)); netlink_head->nlmsg_type = operate | (NFNL_SUBSYS_IPSET << 8); netlink_head->nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE; netlink_msg = (struct ipset_netlink_msg *)(buffer + netlink_head->nlmsg_len); netlink_head->nlmsg_len += NETLINK_ALIGN(sizeof(struct ipset_netlink_msg)); netlink_msg->family = af; netlink_msg->version = NFNETLINK_V0; netlink_msg->res_id = htons(NFNL_SUBSYS_IPSET); proto = IPSET_PROTOCOL; _ipset_add_attr(netlink_head, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto); _ipset_add_attr(netlink_head, IPSET_ATTR_SETNAME, strlen(ipset_name) + 1, ipset_name); nested[0] = (struct ipset_netlink_attr *)(buffer + NETLINK_ALIGN(netlink_head->nlmsg_len)); netlink_head->nlmsg_len += NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)); nested[0]->type = NLA_F_NESTED | IPSET_ATTR_DATA; nested[1] = (struct ipset_netlink_attr *)(buffer + NETLINK_ALIGN(netlink_head->nlmsg_len)); netlink_head->nlmsg_len += NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)); nested[1]->type = NLA_F_NESTED | IPSET_ATTR_IP; _ipset_add_attr(netlink_head, (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER, addr_len, addr); nested[1]->len = (void *)buffer + NETLINK_ALIGN(netlink_head->nlmsg_len) - (void *)nested[1]; if (timeout > 0) { expire = htonl(timeout); _ipset_add_attr(netlink_head, IPSET_ATTR_TIMEOUT | NLA_F_NET_BYTEORDER, sizeof(expire), &expire); } nested[0]->len = (void *)buffer + NETLINK_ALIGN(netlink_head->nlmsg_len) - (void *)nested[0]; for (;;) { rc = sendto(ipset_fd, buffer, netlink_head->nlmsg_len, 0, (const struct sockaddr *)&snl, sizeof(snl)); if (rc >= 0) { break; } if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { struct timespec waiter; waiter.tv_sec = 0; waiter.tv_nsec = 10000; nanosleep(&waiter, NULL); continue; } } return rc; } int ipset_add(const char *ipset_name, const unsigned char addr[], int addr_len, unsigned long timeout) { return _ipset_operate(ipset_name, addr, addr_len, timeout, IPSET_ADD); } int ipset_del(const char *ipset_name, const unsigned char addr[], int addr_len) { return _ipset_operate(ipset_name, addr, addr_len, 0, IPSET_DEL); } ================================================ FILE: src/utils/misc.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/dns.h" #include "smartdns/util.h" #include #include #include #include #include #include #include #include #include unsigned long get_tick_count(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000); } unsigned long long get_utc_time_ms(void) { struct timeval tv; gettimeofday(&tv, NULL); unsigned long long millisecondsSinceEpoch = (unsigned long long)(tv.tv_sec) * 1000 + (unsigned long long)(tv.tv_usec) / 1000; return millisecondsSinceEpoch; } char *dir_name(char *path) { if (strstr(path, "/") == NULL) { safe_strncpy(path, "./", PATH_MAX); return path; } return dirname(path); } int create_dir_with_perm(const char *dir_path) { uid_t uid = 0; gid_t gid = 0; struct stat sb; char data_dir[PATH_MAX] = {0}; int unused __attribute__((unused)) = 0; safe_strncpy(data_dir, dir_path, PATH_MAX); dir_name(data_dir); if (get_uid_gid(&uid, &gid) != 0) { return -1; } if (stat(data_dir, &sb) == 0) { if (sb.st_uid == uid && sb.st_gid == gid && (sb.st_mode & 0700) == 0700) { return 0; } if (sb.st_gid == gid && (sb.st_mode & 0070) == 0070) { return 0; } if (sb.st_uid != uid && sb.st_gid != gid && (sb.st_mode & 0007) == 0007) { return 0; } } mkdir(data_dir, 0750); if (chown(data_dir, uid, gid) != 0) { return -2; } unused = chmod(data_dir, 0750); unused = chown(dir_path, uid, gid); return 0; } char *reverse_string(char *output, const char *input, int len, int to_lower_case) { char *begin = output; if (len <= 0) { *output = 0; return output; } len--; while (len >= 0) { *output = *(input + len); if (to_lower_case) { if (*output >= 'A' && *output <= 'Z') { /* To lower case */ *output = *output + 32; } } output++; len--; } *output = 0; return begin; } char *to_lower_case(char *output, const char *input, int len) { char *begin = output; int i = 0; if (len <= 0) { *output = 0; return output; } len--; while (i < len && *(input + i) != '\0') { *output = *(input + i); if (*output >= 'A' && *output <= 'Z') { /* To lower case */ *output = *output + 32; } output++; i++; } *output = 0; return begin; } int full_path(char *normalized_path, int normalized_path_len, const char *path) { const char *p = path; if (path == NULL || normalized_path == NULL) { return -1; } while (*p == ' ') { p++; } if (*p == '\0' || *p == '/') { return -1; } char buf[PATH_MAX]; snprintf(normalized_path, normalized_path_len, "%s/%s", getcwd(buf, sizeof(buf)), path); return 0; } void get_compiled_time(struct tm *tm) { char s_month[5]; int month = 0; int day = 0; int year = 0; int hour = 0; int min = 0; int sec = 0; static const char *month_names = "JanFebMarAprMayJunJulAugSepOctNovDec"; sscanf(__DATE__, "%4s %d %d", s_month, &day, &year); month = (strstr(month_names, s_month) - month_names) / 3; sscanf(__TIME__, "%d:%d:%d", &hour, &min, &sec); tm->tm_year = year - 1900; tm->tm_mon = month; tm->tm_mday = day; tm->tm_isdst = -1; tm->tm_hour = hour; tm->tm_min = min; tm->tm_sec = sec; } unsigned long get_system_mem_size(void) { struct sysinfo memInfo; sysinfo(&memInfo); long long totalMem = memInfo.totalram; totalMem *= memInfo.mem_unit; return totalMem; } int is_numeric(const char *str) { while (*str != '\0') { if (*str < '0' || *str > '9') { return -1; } str++; } return 0; } int has_network_raw_cap(void) { int fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (fd < 0) { return 0; } close(fd); return 1; } uint64_t get_free_space(const char *path) { uint64_t size = 0; struct statvfs buf; if (statvfs(path, &buf) != 0) { return 0; } size = (uint64_t)buf.f_frsize * buf.f_bavail; return size; } int parser_mac_address(const char *in_mac, uint8_t mac[6]) { int fileld_num = 0; if (in_mac == NULL) { return -1; } fileld_num = sscanf(in_mac, "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]); if (fileld_num == 6) { return 0; } fileld_num = sscanf(in_mac, "%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]); if (fileld_num == 6) { return 0; } return -1; } int set_http_host(const char *uri_host, int port, int default_port, char *host) { int is_ipv6; if (uri_host == NULL || port <= 0 || host == NULL) { return -1; } is_ipv6 = check_is_ipv6(uri_host); if (port == default_port) { snprintf(host, DNS_MAX_CNAME_LEN, "%s%s%s", is_ipv6 == 0 ? "[" : "", uri_host, is_ipv6 == 0 ? "]" : ""); } else { snprintf(host, DNS_MAX_CNAME_LEN, "%s%s%s:%d", is_ipv6 == 0 ? "[" : "", uri_host, is_ipv6 == 0 ? "]" : "", port); } return 0; } int decode_hex(int ch) { if ('0' <= ch && ch <= '9') { return ch - '0'; } else if ('A' <= ch && ch <= 'F') { return ch - 'A' + 0xa; } else if ('a' <= ch && ch <= 'f') { return ch - 'a' + 0xa; } else { return -1; } } ================================================ FILE: src/utils/neighbors.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/util.h" #include #include #include #include #include static int netlink_neighbor_fd; int netlink_get_neighbors(int family, const uint8_t *target_ip, int target_ip_len, int (*callback)(const uint8_t *net_addr, int net_addr_len, const uint8_t mac[6], void *arg), void *arg) { if (netlink_neighbor_fd <= 0) { netlink_neighbor_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_ROUTE); if (netlink_neighbor_fd < 0) { errno = EINVAL; return -1; } } struct nlmsghdr *nlh; struct ndmsg *ndm; char buf[1024 * 16]; struct iovec iov = {buf, sizeof(buf)}; struct sockaddr_nl sa; struct msghdr msg; int len; int ret = 0; int send_count = 0; memset(buf, 0, sizeof(buf)); memset(&sa, 0, sizeof(sa)); memset(&msg, 0, sizeof(msg)); sa.nl_family = AF_NETLINK; msg.msg_name = &sa; msg.msg_namelen = sizeof(sa); msg.msg_iov = &iov; msg.msg_iovlen = 1; nlh = (struct nlmsghdr *)buf; nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); nlh->nlmsg_type = RTM_GETNEIGH; nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = time(NULL); nlh->nlmsg_pid = getpid(); ndm = NLMSG_DATA(nlh); memset(ndm, 0, sizeof(struct ndmsg)); ndm->ndm_family = family; if (target_ip_len > 0 && target_ip != NULL) { struct rtattr *rta = (struct rtattr *)(((char *)nlh) + NLMSG_ALIGN(nlh->nlmsg_len)); rta->rta_type = NDA_DST; rta->rta_len = RTA_LENGTH(target_ip_len); memcpy(RTA_DATA(rta), target_ip, target_ip_len); nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len); } while (1) { if (send_count > 5) { errno = ETIMEDOUT; return -1; } send_count++; if (send(netlink_neighbor_fd, buf, NLMSG_SPACE(sizeof(struct ndmsg)), 0) < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { struct timespec waiter; waiter.tv_sec = 0; waiter.tv_nsec = 500000; nanosleep(&waiter, NULL); continue; } close(netlink_neighbor_fd); netlink_neighbor_fd = -1; return -1; } break; } int is_received = 0; int recv_count = 0; while (1) { recv_count++; len = recvmsg(netlink_neighbor_fd, &msg, 0); if (len < 0) { if (recv_count > 5 && is_received == 0) { errno = ETIMEDOUT; return -1; } if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { if (is_received) { break; } struct timespec waiter; waiter.tv_sec = 0; waiter.tv_nsec = 500000; nanosleep(&waiter, NULL); continue; } return -1; } if (ret != 0) { continue; } is_received = 1; uint32_t nlh_len = len; for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, nlh_len); nlh = NLMSG_NEXT(nlh, nlh_len)) { ndm = NLMSG_DATA(nlh); struct rtattr *rta = RTM_RTA(ndm); const uint8_t *mac = NULL; const uint8_t *net_addr = NULL; int net_addr_len = 0; unsigned int rta_len = RTM_PAYLOAD(nlh); if (rta_len > (sizeof(buf) - ((char *)rta - buf))) { continue; } for (; RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) { if (rta->rta_type == NDA_DST) { if (ndm->ndm_family == AF_INET) { struct in_addr *addr = RTA_DATA(rta); if (IN_MULTICAST(ntohl(addr->s_addr))) { continue; } if (ntohl(addr->s_addr) == 0) { continue; } net_addr = (uint8_t *)&addr->s_addr; net_addr_len = IPV4_ADDR_LEN; } else if (ndm->ndm_family == AF_INET6) { struct in6_addr *addr = RTA_DATA(rta); if (IN6_IS_ADDR_MC_NODELOCAL(addr)) { continue; } if (IN6_IS_ADDR_MC_LINKLOCAL(addr)) { continue; } if (IN6_IS_ADDR_MC_SITELOCAL(addr)) { continue; } if (IN6_IS_ADDR_UNSPECIFIED(addr)) { continue; } net_addr = addr->s6_addr; net_addr_len = IPV6_ADDR_LEN; } } else if (rta->rta_type == NDA_LLADDR) { mac = RTA_DATA(rta); if (mac[0] == 0 && mac[1] == 0 && mac[2] == 0 && mac[3] == 0 && mac[4] == 0 && mac[5] == 0) { continue; } } } if (net_addr != NULL && mac != NULL) { ret = callback(net_addr, net_addr_len, mac, arg); if (ret != 0) { break; } } } } return ret; } ================================================ FILE: src/utils/net.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/dns.h" #include "smartdns/lib/jhash.h" #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #include #include #include #include #include #include #include #include #include #include char *get_host_by_addr(char *host, int maxsize, const struct sockaddr *addr) { struct sockaddr_storage *addr_store = (struct sockaddr_storage *)addr; host[0] = 0; switch (addr_store->ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)addr; inet_ntop(AF_INET, &addr_in->sin_addr, host, maxsize); } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { struct sockaddr_in addr_in4; memset(&addr_in4, 0, sizeof(addr_in4)); memcpy(&addr_in4.sin_addr.s_addr, addr_in6->sin6_addr.s6_addr + 12, sizeof(addr_in4.sin_addr.s_addr)); inet_ntop(AF_INET, &addr_in4.sin_addr, host, maxsize); } else { inet_ntop(AF_INET6, &addr_in6->sin6_addr, host, maxsize); } } break; default: goto errout; break; } return host; errout: return NULL; } int generate_random_addr(unsigned char *addr, int addr_len, int mask) { if (mask / 8 > addr_len) { return -1; } int offset = mask / 8; int bit = 0; for (int i = offset; i < addr_len; i++) { bit = 0xFF; if (i == offset) { bit = ~(0xFF << (8 - mask % 8)) & 0xFF; } addr[i] = jhash(&addr[i], 1, 0) & bit; } return 0; } int generate_addr_map(const unsigned char *addr_from, const unsigned char *addr_to, unsigned char *addr_out, int addr_len, int mask) { if ((mask / 8) >= addr_len) { if (mask % 8 != 0) { return -1; } } int offset = mask / 8; int bit = mask % 8; for (int i = 0; i < offset; i++) { addr_out[i] = addr_to[i]; } if (bit != 0) { int mask1 = 0xFF >> bit; int mask2 = (0xFF << (8 - bit)) & 0xFF; addr_out[offset] = addr_from[offset] & mask1; addr_out[offset] |= addr_to[offset] & mask2; offset = offset + 1; } for (int i = offset; i < addr_len; i++) { addr_out[i] = addr_from[i]; } return 0; } int is_private_addr(const unsigned char *addr, int addr_len) { if (addr_len == IPV4_ADDR_LEN) { if (addr[0] == 10) { return 1; } if (addr[0] == 172 && addr[1] >= 16 && addr[1] <= 31) { return 1; } if (addr[0] == 192 && addr[1] == 168) { return 1; } } else if (addr_len == IPV6_ADDR_LEN) { if (addr[0] == 0xFD) { return 1; } if (addr[0] == 0xFE && addr[1] == 0x80) { return 1; } } return 0; } int is_private_addr_sockaddr(const struct sockaddr *addr, socklen_t addr_len) { switch (addr->sa_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)addr; return is_private_addr((const unsigned char *)&addr_in->sin_addr.s_addr, IPV4_ADDR_LEN); } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { return is_private_addr(addr_in6->sin6_addr.s6_addr + 12, IPV4_ADDR_LEN); } else { return is_private_addr(addr_in6->sin6_addr.s6_addr, IPV6_ADDR_LEN); } } break; default: goto errout; break; } errout: return 0; } int getaddr_by_host(const char *host, struct sockaddr *addr, socklen_t *addr_len) { struct addrinfo hints; struct addrinfo *result = NULL; int ret = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo(host, "53", &hints, &result); if (ret != 0) { goto errout; } if (result->ai_addrlen > *addr_len) { result->ai_addrlen = *addr_len; } addr->sa_family = result->ai_family; memcpy(addr, result->ai_addr, result->ai_addrlen); *addr_len = result->ai_addrlen; freeaddrinfo(result); return 0; errout: if (result) { freeaddrinfo(result); } return -1; } int get_raw_addr_by_sockaddr(const struct sockaddr_storage *addr, int addr_len, unsigned char *raw_addr, int *raw_addr_len) { switch (addr->ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)addr; if (*raw_addr_len < DNS_RR_A_LEN) { goto errout; } memcpy(raw_addr, &addr_in->sin_addr.s_addr, DNS_RR_A_LEN); *raw_addr_len = DNS_RR_A_LEN; } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { if (*raw_addr_len < DNS_RR_A_LEN) { goto errout; } memcpy(raw_addr, addr_in6->sin6_addr.s6_addr + 12, DNS_RR_A_LEN); *raw_addr_len = DNS_RR_A_LEN; } else { if (*raw_addr_len < DNS_RR_AAAA_LEN) { goto errout; } memcpy(raw_addr, addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN); *raw_addr_len = DNS_RR_AAAA_LEN; } } break; default: goto errout; break; } return 0; errout: return -1; } int get_raw_addr_by_ip(const char *ip, unsigned char *raw_addr, int *raw_addr_len) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); if (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) { goto errout; } return get_raw_addr_by_sockaddr(&addr, addr_len, raw_addr, raw_addr_len); errout: return -1; } int getsocket_inet(int fd, struct sockaddr *addr, socklen_t *addr_len) { struct sockaddr_storage addr_store; socklen_t addr_store_len = sizeof(addr_store); if (getsockname(fd, (struct sockaddr *)&addr_store, &addr_store_len) != 0) { goto errout; } switch (addr_store.ss_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)&addr_store; addr_in->sin_family = AF_INET; *addr_len = sizeof(struct sockaddr_in); memcpy(addr, addr_in, sizeof(struct sockaddr_in)); } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)&addr_store; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { struct sockaddr_in addr_in4; memset(&addr_in4, 0, sizeof(addr_in4)); memcpy(&addr_in4.sin_addr.s_addr, addr_in6->sin6_addr.s6_addr + 12, sizeof(addr_in4.sin_addr.s_addr)); addr_in4.sin_family = AF_INET; addr_in4.sin_port = 0; *addr_len = sizeof(struct sockaddr_in); memcpy(addr, &addr_in4, sizeof(struct sockaddr_in)); } else { addr_in6->sin6_family = AF_INET6; *addr_len = sizeof(struct sockaddr_in6); memcpy(addr, addr_in6, sizeof(struct sockaddr_in6)); } } break; default: goto errout; break; } return 0; errout: return -1; } int fill_sockaddr_by_ip(unsigned char *ip, int ip_len, int port, struct sockaddr *addr, socklen_t *addr_len) { if (ip == NULL || addr == NULL || addr_len == NULL) { return -1; } if (ip_len == IPV4_ADDR_LEN) { struct sockaddr_in *addr_in = NULL; addr->sa_family = AF_INET; addr_in = (struct sockaddr_in *)addr; addr_in->sin_port = htons(port); addr_in->sin_family = AF_INET; memcpy(&addr_in->sin_addr.s_addr, ip, ip_len); *addr_len = 16; } else if (ip_len == IPV6_ADDR_LEN) { struct sockaddr_in6 *addr_in6 = NULL; addr->sa_family = AF_INET6; addr_in6 = (struct sockaddr_in6 *)addr; addr_in6->sin6_port = htons(port); addr_in6->sin6_family = AF_INET6; memcpy(addr_in6->sin6_addr.s6_addr, ip, ip_len); *addr_len = 28; } return -1; } int check_is_ipv4(const char *ip) { const char *ptr = ip; char c = 0; int dot_num = 0; int dig_num = 0; while ((c = *ptr++) != '\0') { if (c == '.') { dot_num++; dig_num = 0; continue; } /* check number count of one field */ if (dig_num >= 4) { return -1; } if (c >= '0' && c <= '9') { dig_num++; continue; } return -1; } /* check field number */ if (dot_num != 3) { return -1; } return 0; } int check_is_ipv6(const char *ip) { const char *ptr = ip; char c = 0; int colon_num = 0; int dig_num = 0; while ((c = *ptr++) != '\0') { if (c == '[' || c == ']') { continue; } /* scope id, end of ipv6 address*/ if (c == '%') { break; } if (c == ':') { colon_num++; dig_num = 0; continue; } /* check number count of one field */ if (dig_num >= 5) { return -1; } dig_num++; if (c >= '0' && c <= '9') { continue; } if (c >= 'a' && c <= 'f') { continue; } if (c >= 'A' && c <= 'F') { continue; } return -1; } /* check field number */ if (colon_num > 7) { return -1; } return 0; } int check_is_ipaddr(const char *ip) { if (strstr(ip, ".")) { /* IPV4 */ return check_is_ipv4(ip); } else if (strstr(ip, ":")) { /* IPV6 */ return check_is_ipv6(ip); } return -1; } int set_fd_nonblock(int fd, int nonblock) { int ret = 0; int flags = fcntl(fd, F_GETFL); if (flags == -1) { return -1; } flags = (nonblock) ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK); ret = fcntl(fd, F_SETFL, flags); if (ret == -1) { return -1; } return 0; } int set_sock_keepalive(int fd, int keepidle, int keepinterval, int keepcnt) { const int yes = 1; if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) != 0) { return -1; } setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)); setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepinterval, sizeof(keepinterval)); setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)); return 0; } int set_sock_lingertime(int fd, int time) { struct linger l; l.l_onoff = 1; l.l_linger = 0; if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (const char *)&l, sizeof(l)) != 0) { return -1; } return 0; } int has_unprivileged_ping(void) { int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); if (fd < 0) { return 0; } close(fd); fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); if (fd < 0) { return 0; } close(fd); return 1; } ================================================ FILE: src/utils/nftset.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/lib/nftset.h" #include "smartdns/dns_conf.h" #include "smartdns/tlog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NFNL_SUBSYS_NFTABLES #include struct nlmsgreq { struct nlmsghdr h; struct nfgenmsg m; }; enum { PAYLOAD_MAX = 2048 }; static int nftset_fd; static int _nftset_get_nffamily_from_str(const char *family) { if (strncmp(family, "inet", sizeof("inet")) == 0) { return NFPROTO_INET; } else if (strncmp(family, "ip", sizeof("ip")) == 0) { return NFPROTO_IPV4; } else if (strncmp(family, "ip6", sizeof("ip6")) == 0) { return NFPROTO_IPV6; } else if (strncmp(family, "arp", sizeof("arp")) == 0) { return NFPROTO_ARP; } else if (strncmp(family, "netdev", sizeof("netdev")) == 0) { return NFPROTO_NETDEV; } else if (strncmp(family, "bridge", sizeof("bridge")) == 0) { return NFPROTO_BRIDGE; } else if (strncmp(family, "decnet", sizeof("decnet")) == 0) { return NFPROTO_DECNET; } else { return NFPROTO_UNSPEC; } } static struct rtattr *_nftset_nlmsg_tail(struct nlmsghdr *n) { return (struct rtattr *)((uint8_t *)n + NLMSG_ALIGN(n->nlmsg_len)); } static int _nftset_addattr(struct nlmsghdr *n, int maxlen, __u16 type, const void *data, __u16 alen) { const __u16 len = RTA_LENGTH(alen); const ssize_t newlen = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); if (newlen > maxlen) { errno = ENOSPC; return -1; } struct rtattr *attr = _nftset_nlmsg_tail(n); attr->rta_len = len; attr->rta_type = type; void *rta_data = RTA_DATA(attr); if ((data != NULL) && (alen > 0)) { memcpy(rta_data, data, alen); } memset((uint8_t *)rta_data + alen, 0, RTA_ALIGN(len) - len); n->nlmsg_len = newlen; return 0; } static int _nftset_addattr_string(struct nlmsghdr *n, int maxlen, __u16 type, const char *s) { return _nftset_addattr(n, maxlen, type, s, strlen(s) + 1); } static int __attribute__((unused)) _nftset_addattr_uint32(struct nlmsghdr *n, int maxlen, __u16 type, const uint32_t v) { return _nftset_addattr(n, maxlen, type, &v, sizeof(uint32_t)); } static int __attribute__((unused)) _nftset_addattr_uint16(struct nlmsghdr *n, int maxlen, __u16 type, const uint16_t v) { return _nftset_addattr(n, maxlen, type, &v, sizeof(uint16_t)); } static int __attribute__((unused)) _nftset_addattr_uint8(struct nlmsghdr *n, int maxlen, __u16 type, const uint8_t v) { return _nftset_addattr(n, maxlen, type, &v, sizeof(uint8_t)); } static struct rtattr *_nftset_addattr_nest(struct nlmsghdr *n, int maxlen, __u16 type) { struct rtattr *attr = _nftset_nlmsg_tail(n); if (-1 == _nftset_addattr(n, maxlen, type, NULL, 0)) { return NULL; } return attr; } static void _nftset_addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest) { const void *tail = _nftset_nlmsg_tail(n); nest->rta_len = (uint8_t *)tail - (uint8_t *)nest; } static int _nftset_start_batch(void *buf, void **nextbuf) { struct nlmsgreq *req = (struct nlmsgreq *)buf; memset(buf, 0, sizeof(struct nlmsgreq)); req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg)); req->h.nlmsg_flags = NLM_F_REQUEST; req->h.nlmsg_type = NFNL_MSG_BATCH_BEGIN; req->h.nlmsg_seq = time(NULL); req->m.res_id = htons(NFNL_SUBSYS_NFTABLES); if (nextbuf) { *nextbuf = (uint8_t *)buf + req->h.nlmsg_len; } return 0; } static int _nftset_end_batch(void *buf, void **nextbuf) { struct nlmsgreq *req = (struct nlmsgreq *)buf; memset(buf, 0, sizeof(struct nlmsgreq)); req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg)); req->h.nlmsg_flags = NLM_F_REQUEST; req->h.nlmsg_type = NFNL_MSG_BATCH_END; req->h.nlmsg_seq = time(NULL); req->m.res_id = htons(NFNL_SUBSYS_NFTABLES); if (nextbuf) { *nextbuf = (uint8_t *)buf + req->h.nlmsg_len; } return 0; } static int _nftset_socket_init(void) { struct sockaddr_nl addr = {0}; addr.nl_family = AF_NETLINK; addr.nl_pid = 0; int fd = -1; if (nftset_fd > 0) { return 0; } fd = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_NETFILTER); if (fd < 0) { return -1; } if (bind(fd, (struct sockaddr *)(&addr), sizeof(addr)) < 0) { close(fd); return -2; } nftset_fd = fd; return 0; } static int _nftset_socket_request(void *msg, int msg_len, void *ret_msg, int ret_msg_len) { int ret = -1; struct pollfd pfds; int do_recv = 0; int len = 0; const int max_retries = 3; int retry_count = 0; if (_nftset_socket_init() != 0) { if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset socket init failed, errno=%d", errno); } return -1; } /* clear pending error message*/ for (;;) { uint8_t buff[1024]; ret = recv(nftset_fd, buff, sizeof(buff), MSG_DONTWAIT); if (ret < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset clear pending msg error, errno=%d", errno); } } break; } } /* send message with retry */ while (retry_count < max_retries) { len = send(nftset_fd, msg, msg_len, 0); if (len == msg_len) { break; } if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { retry_count++; struct timespec waiter; waiter.tv_sec = 0; waiter.tv_nsec = 10000 * retry_count; nanosleep(&waiter, NULL); continue; } if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset send msg failed, errno=%d, retries=%d", errno, retry_count); } return -1; } if (retry_count >= max_retries) { if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset send msg failed after max retries"); } return -1; } if (ret_msg == NULL || ret_msg_len <= 0) { return 0; } pfds.fd = nftset_fd; pfds.events = POLLIN; pfds.revents = 0; ret = poll(&pfds, 1, 100); if (ret <= 0) { if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset poll timeout or error, ret=%d, errno=%d", ret, errno); } return -1; } if ((pfds.revents & POLLIN) == 0) { if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset poll no data, revents=0x%x", pfds.revents); } return -1; } memset(ret_msg, 0, ret_msg_len); len = 0; for (;;) { ret = recv(nftset_fd, ret_msg + len, ret_msg_len - len, 0); if (ret < 0) { if (errno == EAGAIN && do_recv == 1) { break; } if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset recv msg failed, errno=%d", errno); } return -1; } do_recv = 1; len += ret; if (len >= ret_msg_len) { break; } struct nlmsghdr *nlh = (struct nlmsghdr *)ret_msg; if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh); if (err->error != 0) { errno = -err->error; if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset recv error msg, error=%d", err->error); } return -1; } continue; } /* Correctly handle the NFT_MSG_GETSETELEM response */ if (nlh->nlmsg_type == (NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_NEWSETELEM)) { break; } if (nlh->nlmsg_type == (NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_GETSETELEM)) { continue; } if (nlh->nlmsg_type & (NFNL_SUBSYS_NFTABLES << 8)) { if (nlh->nlmsg_type & NLMSG_DONE) { break; } } errno = ENOTSUP; if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset unsupported msg type, type=0x%x", nlh->nlmsg_type); } return -1; } return 0; } static int _nftset_socket_send(void *msg, int msg_len) { char recvbuff[1024]; if (dns_conf.nftset_debug_enable == 0) { return _nftset_socket_request(msg, msg_len, NULL, 0); } return _nftset_socket_request(msg, msg_len, recvbuff, sizeof(recvbuff)); } static int _nftset_get_nftset(int nffamily, const char *table_name, const char *setname, void *buf, void **nextbuf) { struct nlmsgreq *req = (struct nlmsgreq *)buf; memset(buf, 0, sizeof(struct nlmsgreq)); req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg)); req->h.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; req->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_GETSET; req->h.nlmsg_seq = time(NULL); req->m.nfgen_family = nffamily; req->m.res_id = htons(NFNL_SUBSYS_NFTABLES); req->m.version = 0; struct nlmsghdr *n = &req->h; _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname); _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, table_name); if (nextbuf) { *nextbuf = (uint8_t *)buf + req->h.nlmsg_len; } return 0; } static int _nftset_get_flags(int nffamily, const char *tablename, const char *setname, uint32_t *flags) { uint8_t buf[PAYLOAD_MAX]; uint8_t result[PAYLOAD_MAX]; void *next = buf; int buffer_len = 0; if (flags == NULL) { return -1; } _nftset_get_nftset(nffamily, tablename, setname, next, &next); buffer_len = (uint8_t *)next - buf; int ret = _nftset_socket_request(buf, buffer_len, result, sizeof(result)); if (ret < 0) { return -1; } struct nlmsghdr *nlh = (struct nlmsghdr *)result; struct nfgenmsg *nfmsg = (struct nfgenmsg *)NLMSG_DATA(nlh); struct nfattr *nfa = (struct nfattr *)NFM_NFA(nfmsg); *flags = 0; for (; NFA_OK(nfa, nlh->nlmsg_len); nfa = NFA_NEXT(nfa, nlh->nlmsg_len)) { if (nfa->nfa_type == NFTA_SET_FLAGS) { *flags = ntohl(*(uint32_t *)NFA_DATA(nfa)); break; } } return 0; } static int _nftset_del_element(int nffamily, const char *table_name, const char *setname, const void *data, int data_len, const void *data_interval, int data_interval_len, void *buf, void **nextbuf) { struct nlmsgreq *req = (struct nlmsgreq *)buf; memset(buf, 0, sizeof(struct nlmsgreq)); req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg)); req->h.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE; req->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_DELSETELEM; req->h.nlmsg_seq = time(NULL); if (dns_conf.nftset_debug_enable) { req->h.nlmsg_flags |= NLM_F_ACK; } req->m.nfgen_family = nffamily; struct nlmsghdr *n = &req->h; _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, table_name); _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname); struct rtattr *nest_list = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_LIST_ELEMENTS); struct rtattr *nest_elem = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED); struct rtattr *nest_elem_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY); _nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data, data_len); _nftset_addattr_nest_end(n, nest_elem_key); _nftset_addattr_nest_end(n, nest_elem); /* interval attribute */ if (data_interval && data_interval_len > 0) { struct rtattr *nest_interval_end = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_LIST_ELEM); _nftset_addattr_uint32(n, PAYLOAD_MAX, NFTA_SET_ELEM_FLAGS, htonl(NFT_SET_ELEM_INTERVAL_END)); struct rtattr *nest_elem_interval_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY); _nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data_interval, data_interval_len); _nftset_addattr_nest_end(n, nest_elem_interval_key); _nftset_addattr_nest_end(n, nest_interval_end); } _nftset_addattr_nest_end(n, nest_list); if (nextbuf) { *nextbuf = (uint8_t *)buf + req->h.nlmsg_len; } return 0; } static int _nftset_add_element(int nffamily, const char *table_name, const char *setname, const void *data, int data_len, const void *data_interval, int data_interval_len, unsigned long timeout, void *buf, void **nextbuf) { struct nlmsgreq *req = (struct nlmsgreq *)buf; memset(buf, 0, sizeof(struct nlmsgreq)); req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg)); req->h.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE; req->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_NEWSETELEM; req->h.nlmsg_seq = time(NULL); if (dns_conf.nftset_debug_enable) { req->h.nlmsg_flags |= NLM_F_ACK; } req->m.nfgen_family = nffamily; struct nlmsghdr *n = &req->h; _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, table_name); _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname); struct rtattr *nest_list = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_LIST_ELEMENTS); struct rtattr *nest_elem = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_LIST_ELEM); struct rtattr *nest_elem_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY); _nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data, data_len); _nftset_addattr_nest_end(n, nest_elem_key); if (timeout > 0) { uint64_t timeout_value = htobe64(timeout * 1000); _nftset_addattr(n, PAYLOAD_MAX, NFTA_SET_ELEM_TIMEOUT, &timeout_value, sizeof(timeout_value)); } _nftset_addattr_nest_end(n, nest_elem); /* interval attribute */ if (data_interval && data_interval_len > 0) { struct rtattr *nest_interval_end = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_LIST_ELEM); _nftset_addattr_uint32(n, PAYLOAD_MAX, NFTA_SET_ELEM_FLAGS, htonl(NFT_SET_ELEM_INTERVAL_END)); struct rtattr *nest_elem_interval_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY); _nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data_interval, data_interval_len); _nftset_addattr_nest_end(n, nest_elem_interval_key); _nftset_addattr_nest_end(n, nest_interval_end); } _nftset_addattr_nest_end(n, nest_list); if (nextbuf) { *nextbuf = (uint8_t *)buf + req->h.nlmsg_len; } return 0; } static int _nftset_process_setflags(uint32_t flags, const unsigned char addr[], int addr_len, unsigned long *timeout, uint8_t **interval_addr, int *interval_addr_len) { uint8_t *addr_end = *interval_addr; if ((flags & NFT_SET_TIMEOUT) == 0 && timeout != NULL) { *timeout = 0; } if ((flags & NFT_SET_INTERVAL) && addr_end != NULL) { if (addr_len == 4) { addr_end[0] = addr[0]; addr_end[1] = addr[1]; addr_end[2] = addr[2]; addr_end[3] = addr[3] + 1; if (addr_end[3] == 0) { return -1; } *interval_addr_len = 4; } else if (addr_len == 16) { memcpy(addr_end, addr, 16); addr_end[15] = addr[15] + 1; if (addr_end[15] == 0) { return -1; } *interval_addr_len = 16; } } else { *interval_addr = NULL; *interval_addr_len = 0; } return 0; } /* Check whether the IP address already exists in the nftables collection */ static int _nftset_test_ip_exists(int nffamily, const char *tablename, const char *setname, const unsigned char addr[], int addr_len) { uint8_t buf[PAYLOAD_MAX]; uint8_t result[PAYLOAD_MAX]; void *next = buf; int buffer_len = 0; /* Initialize test message */ struct nlmsgreq *req = (struct nlmsgreq *)next; memset(next, 0, sizeof(struct nlmsgreq)); req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg)); req->h.nlmsg_flags = NLM_F_REQUEST; req->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_GETSETELEM; req->h.nlmsg_seq = time(NULL); req->m.nfgen_family = nffamily; req->m.res_id = htons(NFNL_SUBSYS_NFTABLES); req->m.version = 0; struct nlmsghdr *n = &req->h; _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, tablename); _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname); struct rtattr *nest_list = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_LIST_ELEMENTS); struct rtattr *nest_elem = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_LIST_ELEM); struct rtattr *nest_elem_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY); _nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, addr, addr_len); _nftset_addattr_nest_end(n, nest_elem_key); _nftset_addattr_nest_end(n, nest_elem); _nftset_addattr_nest_end(n, nest_list); next = (uint8_t *)next + req->h.nlmsg_len; buffer_len = (uint8_t *)next - buf; if (dns_conf.nftset_debug_enable) { char ip_str[INET6_ADDRSTRLEN]; if (addr_len == 4) { inet_ntop(AF_INET, addr, ip_str, sizeof(ip_str)); } else if (addr_len == 16) { inet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str)); } else { snprintf(ip_str, sizeof(ip_str), "unknown"); } tlog(TLOG_DEBUG, "nftset test ip: family=%d, table=%s, set=%s, ip=%s", nffamily, tablename, setname, ip_str); } /* Send a test message and receive a response */ int ret = _nftset_socket_request(buf, buffer_len, result, sizeof(result)); if (ret < 0) { /* errorno=-2 */ if (errno == ENOENT) { if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset test ip: table/set not found, assuming ip not exists"); } return 0; } /* errorno=11 */ else if (errno == EAGAIN || errno == EWOULDBLOCK) { if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset test ip: EAGAIN error, assuming ip exists"); } return 1; } if (dns_conf.nftset_debug_enable) { char ip_str[INET6_ADDRSTRLEN]; if (addr_len == 4) { inet_ntop(AF_INET, addr, ip_str, sizeof(ip_str)); } else if (addr_len == 16) { inet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str)); } else { snprintf(ip_str, sizeof(ip_str), "unknown"); } tlog(TLOG_DEBUG, "nftset test ip communication failed, assuming ip not exists: family=%d, table=%s, set=%s, ip=%s, " "errorno:%d", nffamily, tablename, setname, ip_str, errno); } return 0; } int ip_exists = 0; struct nlmsghdr *nlh = (struct nlmsghdr *)result; /* If a successful response is received, it indicates that the IP already exists */ if (nlh->nlmsg_type == (NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_NEWSETELEM)) { ip_exists = 1; } /* Received an error message */ if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh); if (err->error == -ENOENT) { ip_exists = 0; } else if (dns_conf.nftset_debug_enable) { tlog(TLOG_DEBUG, "nftset test ip error: family=%d, table=%s, set=%s, error=%d", nffamily, tablename, setname, -err->error); } } if (dns_conf.nftset_debug_enable) { char ip_str[INET6_ADDRSTRLEN]; if (addr_len == 4) { inet_ntop(AF_INET, addr, ip_str, sizeof(ip_str)); } else if (addr_len == 16) { inet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str)); } else { snprintf(ip_str, sizeof(ip_str), "unknown"); } tlog(TLOG_DEBUG, "nftset test ip result: family=%d, table=%s, set=%s, ip=%s, exists=%d", nffamily, tablename, setname, ip_str, ip_exists); } return ip_exists; } static int _nftset_del(int nffamily, const char *tablename, const char *setname, const unsigned char addr[], int addr_len, const unsigned char addr_end[], int addr_end_len) { uint8_t buf[PAYLOAD_MAX]; void *next = buf; int buffer_len = 0; _nftset_start_batch(next, &next); _nftset_del_element(nffamily, tablename, setname, addr, addr_len, addr_end, addr_end_len, next, &next); _nftset_end_batch(next, &next); buffer_len = (uint8_t *)next - buf; return _nftset_socket_send(buf, buffer_len); } int nftset_del(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[], int addr_len) { int nffamily = _nftset_get_nffamily_from_str(familyname); uint8_t addr_end_buff[16] = {0}; uint8_t *addr_end = addr_end_buff; uint32_t flags = 0; int addr_end_len = 0; int ret = -1; ret = _nftset_get_flags(nffamily, tablename, setname, &flags); if (ret == 0) { ret = _nftset_process_setflags(flags, addr, addr_len, NULL, &addr_end, &addr_end_len); if (ret != 0) { return -1; } } else { addr_end = NULL; addr_end_len = 0; } ret = _nftset_del(nffamily, tablename, setname, addr, addr_len, addr_end, addr_end_len); if (ret != 0 && errno != ENOENT) { tlog(TLOG_ERROR, "nftset delete failed, family:%s, table:%s, set:%s, error:%s", familyname, tablename, setname, strerror(errno)); } return ret; } int nftset_add(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[], int addr_len, unsigned long timeout) { int nffamily = _nftset_get_nffamily_from_str(familyname); int ip_exists = _nftset_test_ip_exists(nffamily, tablename, setname, addr, addr_len); if (ip_exists) { if (dns_conf.nftset_debug_enable) { char ip_str[INET6_ADDRSTRLEN]; if (addr_len == 4) { inet_ntop(AF_INET, addr, ip_str, sizeof(ip_str)); } else if (addr_len == 16) { inet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str)); } else { snprintf(ip_str, sizeof(ip_str), "unknown"); } tlog(TLOG_DEBUG, "nftset skip adding existing ip: family=%d, table=%s, set=%s, ip=%s", nffamily, tablename, setname, ip_str); } return 0; } uint8_t buf[PAYLOAD_MAX]; uint8_t addr_end_buff[16] = {0}; uint8_t *addr_end = addr_end_buff; uint32_t flags = 0; int addr_end_len = 0; void *next = buf; int buffer_len = 0; int ret = -1; ret = _nftset_get_flags(nffamily, tablename, setname, &flags); if (ret == 0) { ret = _nftset_process_setflags(flags, addr, addr_len, &timeout, &addr_end, &addr_end_len); if (ret != 0) { char ip_str[INET6_ADDRSTRLEN]; if (addr_len == 4) { inet_ntop(AF_INET, addr, ip_str, sizeof(ip_str)); } else if (addr_len == 16) { inet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str)); } else { snprintf(ip_str, sizeof(ip_str), "unknown"); } if (dns_conf.nftset_debug_enable) { tlog(TLOG_ERROR, "nftset setflags failed, family:%s, table:%s, set:%s, ip:%s, error:%s", familyname, tablename, setname, ip_str, "ip is invalid"); } return -1; } } else { addr_end = NULL; addr_end_len = 0; } if (timeout > 0) { _nftset_del(nffamily, tablename, setname, addr, addr_len, addr_end, addr_end_len); } _nftset_start_batch(next, &next); _nftset_add_element(nffamily, tablename, setname, addr, addr_len, addr_end, addr_end_len, timeout, next, &next); _nftset_end_batch(next, &next); buffer_len = (uint8_t *)next - buf; ret = _nftset_socket_send(buf, buffer_len); if (ret != 0) { char ip_str[INET6_ADDRSTRLEN]; if (addr_len == 4) { inet_ntop(AF_INET, addr, ip_str, sizeof(ip_str)); } else if (addr_len == 16) { inet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str)); } else { snprintf(ip_str, sizeof(ip_str), "unknown"); } tlog(TLOG_ERROR, "nftset add failed, family:%s, table:%s, set:%s, ip:%s, error:%s", familyname, tablename, setname, ip_str, strerror(errno)); } else { if (dns_conf.nftset_debug_enable) { char ip_str[INET6_ADDRSTRLEN]; if (addr_len == 4) { inet_ntop(AF_INET, addr, ip_str, sizeof(ip_str)); } else if (addr_len == 16) { inet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str)); } else { snprintf(ip_str, sizeof(ip_str), "unknown"); } if (timeout > 0) { tlog(TLOG_DEBUG, "nftset add success, family:%s, table:%s, set:%s, ip:%s, timeout:%lu", familyname, tablename, setname, ip_str, timeout); } else { tlog(TLOG_DEBUG, "nftset add success, family:%s, table:%s, set:%s, ip:%s", familyname, tablename, setname, ip_str); } } } return ret; } #else int nftset_add(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[], int addr_len, unsigned long timeout) { return 0; } int nftset_del(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[], int addr_len) { return 0; } #endif ================================================ FILE: src/utils/ssl.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/dns_conf.h" #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #include #include #include #include #include #define DNS_MAX_HOSTNAME_LEN 256 struct DNS_EVP_PKEY_CTX { EVP_PKEY *pkey; #if (OPENSSL_VERSION_NUMBER < 0x30000000L) RSA *rsa; BIGNUM *bn; #endif }; int is_cert_valid(const char *cert_file_path) { struct stat st; BIO *cert_file = NULL; X509 *cert = NULL; int ret = 0; if (stat(cert_file_path, &st) != 0) { return 0; } if (st.st_size <= 0) { return 0; } cert_file = BIO_new_file(cert_file_path, "r"); if (cert_file == NULL) { return 0; } cert = PEM_read_bio_X509_AUX(cert_file, NULL, NULL, NULL); if (cert == NULL) { goto out; } if (X509_get_notAfter(cert) == NULL) { goto out; } if (X509_get_notBefore(cert) == NULL) { goto out; } if (X509_cmp_current_time(X509_get_notAfter(cert)) < 0) { tlog(TLOG_WARN, "cert %s expired", cert_file_path); goto out; } if (X509_cmp_current_time(X509_get_notBefore(cert)) > 0) { tlog(TLOG_WARN, "cert %s not valid yet", cert_file_path); goto out; } ret = 1; out: if (cert) { X509_free(cert); } if (cert_file) { BIO_free(cert_file); } return ret; } int generate_cert_san(char *san, int max_san_len, const char *append_san) { char hostname[DNS_MAX_HOSTNAME_LEN]; char domainname[DNS_MAX_HOSTNAME_LEN]; int san_len = 0; struct ifaddrs *ifaddr = NULL; struct ifaddrs *ifa = NULL; uint8_t addr[16] = {0}; int addr_len = 0; hostname[0] = '\0'; domainname[0] = '\0'; if (san == NULL || max_san_len <= 0) { return -1; } int len = snprintf(san, max_san_len - san_len, "DNS:%s", "smartdns"); if (len < 0 || len >= max_san_len - san_len) { return -1; } san_len += len; if (append_san != NULL && append_san[0] != '\0') { len = snprintf(san + san_len, max_san_len - san_len, ",%s", append_san); if (len < 0 || len >= max_san_len - san_len) { return -1; } san_len += len; } /* get local domain name */ if (getdomainname(domainname, DNS_MAX_HOSTNAME_LEN - 1) == 0) { /* check domain is valid */ if (strncmp(domainname, "(none)", DNS_MAX_HOSTNAME_LEN - 1) == 0) { domainname[0] = '\0'; } if (domainname[0] != '\0') { len = snprintf(san + san_len, max_san_len - san_len, ",DNS:%s", domainname); if (len < 0 || len >= max_san_len - san_len) { return -1; } san_len += len; } } if (gethostname(hostname, DNS_MAX_HOSTNAME_LEN - 1) == 0) { /* check hostname is valid */ if (strncmp(hostname, "(none)", DNS_MAX_HOSTNAME_LEN - 1) == 0) { hostname[0] = '\0'; } if (hostname[0] != '\0') { len = snprintf(san + san_len, max_san_len - san_len, ",DNS:%s", hostname); if (len < 0 || len >= max_san_len - san_len) { return -1; } san_len += len; } } if (dns_conf.server_name[0] != '\0' && strncmp(dns_conf.server_name, "smartdns", DNS_MAX_SERVER_NAME_LEN - 1) != 0) { len = snprintf(san + san_len, max_san_len - san_len, ",DNS:%s", dns_conf.server_name); if (len < 0 || len >= max_san_len - san_len) { return -1; } san_len += len; } if (getifaddrs(&ifaddr) == -1) { return -1; } for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) { continue; } switch (ifa->ifa_addr->sa_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)ifa->ifa_addr; memcpy(addr, &(addr_in->sin_addr.s_addr), 4); addr_len = 4; } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)ifa->ifa_addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { memcpy(addr, &(addr_in6->sin6_addr.s6_addr[12]), 4); addr_len = 4; } else { memcpy(addr, addr_in6->sin6_addr.s6_addr, 16); addr_len = 16; // TODO // SKIP local IPV6; continue; } } break; default: continue; break; } if (is_private_addr(addr, addr_len) == 0) { continue; } if (addr_len == 4) { len = snprintf(san + san_len, max_san_len - san_len, ",IP:%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); } else if (addr_len == 16) { len = snprintf(san + san_len, max_san_len - san_len, ",IP:%x:%x:%x:%x:%x:%x:%x:%x", ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[0]), ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[1]), ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[2]), ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[3]), ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[4]), ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[5]), ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[6]), ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[7])); } else { continue; } if (len < 0 || len >= max_san_len - san_len) { goto errout; } san_len += len; } freeifaddrs(ifaddr); return 0; errout: if (ifaddr) { freeifaddrs(ifaddr); } return -1; } static void _free_key(struct DNS_EVP_PKEY_CTX *ctx) { if (ctx) { if (ctx->pkey) { EVP_PKEY_free(ctx->pkey); } #if (OPENSSL_VERSION_NUMBER < 0x30000000L) if (ctx->rsa) { RSA_free(ctx->rsa); } if (ctx->bn) { BN_free(ctx->bn); } free(ctx); #endif } } static struct DNS_EVP_PKEY_CTX *_read_key_from_file(const char *key_path) { struct DNS_EVP_PKEY_CTX *ctx = NULL; EVP_PKEY *pkey = NULL; BIO *key_file = NULL; ctx = zalloc(1, sizeof(struct DNS_EVP_PKEY_CTX)); if (ctx == NULL) { return NULL; } key_file = BIO_new_file(key_path, "rb"); if (key_file == NULL) { tlog(TLOG_ERROR, "read root key file %s failed.", key_path); goto errout; } pkey = PEM_read_bio_PrivateKey(key_file, NULL, NULL, NULL); if (pkey == NULL) { tlog(TLOG_ERROR, "read root key data failed."); goto errout; } BIO_free(key_file); ctx->pkey = pkey; return ctx; errout: if (key_file) { BIO_free(key_file); } if (ctx) { _free_key(ctx); } return NULL; } static struct DNS_EVP_PKEY_CTX *_generate_key(void) { struct DNS_EVP_PKEY_CTX *ctx = NULL; ctx = zalloc(1, sizeof(struct DNS_EVP_PKEY_CTX)); if (ctx == NULL) { return NULL; } const int RSA_KEY_LENGTH = 2048; #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) ctx->pkey = EVP_RSA_gen(RSA_KEY_LENGTH); #else ctx->pkey = EVP_PKEY_new(); ctx->rsa = RSA_new(); ctx->bn = BN_new(); BN_set_word(ctx->bn, RSA_F4); RSA_generate_key_ex(ctx->rsa, RSA_KEY_LENGTH, ctx->bn, NULL); EVP_PKEY_assign_RSA(ctx->pkey, ctx->rsa); #endif return ctx; } static X509 *_generate_smartdns_cert(EVP_PKEY *pkey, X509 *issuer_cert, EVP_PKEY *issuer_key, const char *san, int days) { X509 *cert = NULL; X509_EXTENSION *cert_ext = NULL; int is_ca = 0; if (pkey == NULL) { goto errout; } cert = X509_new(); if (cert == NULL) { goto errout; } if (issuer_cert == NULL || issuer_key == NULL) { is_ca = 1; } X509_set_version(cert, 2); ASN1_INTEGER_set(X509_get_serialNumber(cert), rand()); X509_gmtime_adj(X509_get_notBefore(cert), 0); X509_gmtime_adj(X509_get_notAfter(cert), days * 24 * 3600); X509_set_pubkey(cert, pkey); X509_NAME *name = X509_get_subject_name(cert); X509_NAME *issuer_name = name; const unsigned char *country = (unsigned char *)"smartdns"; const unsigned char *company = (unsigned char *)"smartdns"; const unsigned char *common_name = (unsigned char *)(is_ca ? "SmartDNS Root" : "smartdns"); const char *CA_BASIC_CONSTRAINTS = is_ca ? "CA:TRUE" : "CA:FALSE"; const char *KEY_USAGE = is_ca ? "keyCertSign,cRLSign" : "digitalSignature,keyEncipherment"; const char *EXT_KEY_USAGE = is_ca ? "clientAuth,serverAuth,codeSigning,timeStamping" : "serverAuth"; X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, country, -1, -1, 0); X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, common_name, -1, -1, 0); if (is_ca) { X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, company, -1, -1, 0); } else { issuer_name = X509_get_subject_name(issuer_cert); } X509_set_subject_name(cert, name); X509_set_issuer_name(cert, issuer_name); if (san != NULL) { cert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, san); if (cert_ext == NULL) { goto errout; } X509_add_ext(cert, cert_ext, -1); X509_EXTENSION_free(cert_ext); } // Add X509v3 extensions cert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_basic_constraints, CA_BASIC_CONSTRAINTS); X509_add_ext(cert, cert_ext, -1); X509_EXTENSION_free(cert_ext); cert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, KEY_USAGE); X509_add_ext(cert, cert_ext, -1); X509_EXTENSION_free(cert_ext); if (EXT_KEY_USAGE != NULL) { cert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, EXT_KEY_USAGE); X509_add_ext(cert, cert_ext, -1); X509_EXTENSION_free(cert_ext); } cert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_key_identifier, "hash"); X509_add_ext(cert, cert_ext, -1); X509_EXTENSION_free(cert_ext); X509_sign(cert, is_ca ? pkey : issuer_key, EVP_sha256()); return cert; errout: if (cert) { X509_free(cert); } return NULL; } int generate_cert_key(const char *key_path, const char *cert_path, const char *root_key_path, const char *san, int days) { char root_key_path_buff[PATH_MAX * 2] = {0}; char server_key_path[PATH_MAX] = {0}; BIO *server_key_file = NULL; BIO *server_cert_file = NULL; BIO *root_key_file = NULL; int create_root_key = 0; X509 *ca_cert = NULL; X509 *server_cert = NULL; struct DNS_EVP_PKEY_CTX *server_key_ctx = NULL; struct DNS_EVP_PKEY_CTX *ca_key_ctx = NULL; if (key_path == NULL || cert_path == NULL) { return -1; } if (root_key_path == NULL || root_key_path[0] == '\0') { safe_strncpy(server_key_path, key_path, sizeof(server_key_path)); if (dir_name(server_key_path) == NULL) { tlog(TLOG_ERROR, "get server key path failed."); return -1; } snprintf(root_key_path_buff, sizeof(root_key_path_buff), "%s/root-ca.key", server_key_path); root_key_path = root_key_path_buff; } if (access(root_key_path, F_OK) == 0) { ca_key_ctx = _read_key_from_file(root_key_path); if (ca_key_ctx == NULL) { tlog(TLOG_ERROR, "read root ca key failed."); goto errout; } create_root_key = 1; } else { ca_key_ctx = _generate_key(); create_root_key = 0; } if (ca_key_ctx == NULL) { tlog(TLOG_ERROR, "generate root ca key failed."); goto errout; } ca_cert = _generate_smartdns_cert(ca_key_ctx->pkey, NULL, NULL, NULL, 365 * 10); if (ca_cert == NULL) { tlog(TLOG_ERROR, "generate root ca cert failed."); goto errout; } server_key_ctx = _generate_key(); if (server_key_ctx == NULL) { tlog(TLOG_ERROR, "generate server key failed."); goto errout; } server_cert = _generate_smartdns_cert(server_key_ctx->pkey, ca_cert, ca_key_ctx->pkey, san, days); if (server_cert == NULL) { tlog(TLOG_ERROR, "generate server cert failed."); goto errout; } server_key_file = BIO_new_file(key_path, "wb"); server_cert_file = BIO_new_file(cert_path, "wb"); if (server_key_file == NULL || server_cert_file == NULL) { tlog(TLOG_ERROR, "create key/cert file failed."); return -1; } if (PEM_write_bio_PrivateKey(server_key_file, server_key_ctx->pkey, NULL, NULL, 0, NULL, NULL) != 1) { return -1; } if (PEM_write_bio_X509(server_cert_file, server_cert) != 1) { return -1; } if (PEM_write_bio_X509(server_cert_file, ca_cert) != 1) { return -1; } if (create_root_key == 0) { root_key_file = BIO_new_file(root_key_path, "wb"); if (root_key_file == NULL) { tlog(TLOG_ERROR, "create root ca key file failed."); goto errout; } if (PEM_write_bio_PrivateKey(root_key_file, ca_key_ctx->pkey, NULL, NULL, 0, NULL, NULL) != 1) { goto errout; } BIO_free_all(root_key_file); chmod(root_key_path, S_IRUSR); } chmod(key_path, S_IRUSR); chmod(cert_path, S_IRUSR); BIO_free_all(server_key_file); BIO_free_all(server_cert_file); X509_free(ca_cert); X509_free(server_cert); _free_key(ca_key_ctx); _free_key(server_key_ctx); return 0; errout: if (server_key_file) { BIO_free_all(server_key_file); } if (server_cert_file) { BIO_free_all(server_cert_file); } if (root_key_file) { BIO_free_all(root_key_file); } if (ca_cert) { X509_free(ca_cert); } if (server_cert) { X509_free(server_cert); } if (ca_key_ctx) { _free_key(ca_key_ctx); } if (server_key_ctx) { _free_key(server_key_ctx); } return -1; } #if OPENSSL_API_COMPAT < 0x10100000 #define THREAD_STACK_SIZE (16 * 1024) static pthread_mutex_t *lock_cs; static long *lock_count; static __attribute__((unused)) void _pthreads_locking_callback(int mode, int type, const char *file, int line) { if (mode & CRYPTO_LOCK) { pthread_mutex_lock(&(lock_cs[type])); lock_count[type]++; } else { pthread_mutex_unlock(&(lock_cs[type])); } } static __attribute__((unused)) unsigned long _pthreads_thread_id(void) { unsigned long ret = 0; ret = (unsigned long)pthread_self(); return (ret); } void SSL_CRYPTO_thread_setup(void) { int i = 0; if (lock_cs != NULL) { return; } lock_cs = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long)); if (!lock_cs || !lock_count) { /* Nothing we can do about this...void function! */ if (lock_cs) { OPENSSL_free(lock_cs); } if (lock_count) { OPENSSL_free(lock_count); } return; } for (i = 0; i < CRYPTO_num_locks(); i++) { lock_count[i] = 0; pthread_mutex_init(&(lock_cs[i]), NULL); } #if OPENSSL_API_COMPAT < 0x10000000 CRYPTO_set_id_callback(_pthreads_thread_id); #else CRYPTO_THREADID_set_callback(_pthreads_thread_id); #endif CRYPTO_set_locking_callback(_pthreads_locking_callback); } void SSL_CRYPTO_thread_cleanup(void) { int i = 0; if (lock_cs == NULL) { return; } CRYPTO_set_locking_callback(NULL); for (i = 0; i < CRYPTO_num_locks(); i++) { pthread_mutex_destroy(&(lock_cs[i])); } OPENSSL_free(lock_cs); OPENSSL_free(lock_count); lock_cs = NULL; lock_count = NULL; } #endif unsigned char *SSL_SHA256(const unsigned char *d, size_t n, unsigned char *md) { static unsigned char m[SHA256_DIGEST_LENGTH]; if (md == NULL) { md = m; } EVP_MD_CTX *ctx = EVP_MD_CTX_create(); if (ctx == NULL) { return NULL; } EVP_MD_CTX_init(ctx); EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); EVP_DigestUpdate(ctx, d, n); EVP_DigestFinal_ex(ctx, m, NULL); EVP_MD_CTX_destroy(ctx); return (md); } int SSL_base64_decode_ext(const char *in, unsigned char *out, int max_outlen, int url_safe, int auto_padding) { size_t inlen = strlen(in); char *in_padding_data = NULL; int padding_len = 0; const char *in_data = in; int outlen = 0; if (inlen == 0) { return 0; } if (inlen % 4 == 0) { auto_padding = 0; } if (auto_padding == 1 || url_safe == 1) { padding_len = 4 - inlen % 4; in_padding_data = (char *)malloc(inlen + padding_len + 1); if (in_padding_data == NULL) { goto errout; } if (url_safe) { for (size_t i = 0; i < inlen; i++) { if (in[i] == '-') { in_padding_data[i] = '+'; } else if (in[i] == '_') { in_padding_data[i] = '/'; } else { in_padding_data[i] = in[i]; } } } else { memcpy(in_padding_data, in, inlen); } if (auto_padding) { memset(in_padding_data + inlen, '=', padding_len); } else { padding_len = 0; } in_padding_data[inlen + padding_len] = '\0'; in_data = in_padding_data; inlen += padding_len; } if (max_outlen < (int)inlen / 4 * 3) { goto errout; } outlen = EVP_DecodeBlock(out, (unsigned char *)in_data, inlen); if (outlen < 0) { goto errout; } /* Subtract padding bytes from |outlen| */ while (in[--inlen] == '=') { --outlen; } if (in_padding_data) { free(in_padding_data); } outlen -= padding_len; return outlen; errout: if (in_padding_data) { free(in_padding_data); } return -1; } int SSL_base64_decode(const char *in, unsigned char *out, int max_outlen) { return SSL_base64_decode_ext(in, out, max_outlen, 0, 0); } int SSL_base64_encode(const void *in, int in_len, char *out) { int outlen = 0; if (in_len == 0) { return 0; } outlen = EVP_EncodeBlock((unsigned char *)out, in, in_len); if (outlen < 0) { goto errout; } return outlen; errout: return -1; } int dns_is_quic_supported(void) { #if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC) return 1; #else return 0; #endif } ================================================ FILE: src/utils/stack.c ================================================ /************************************************************************* * * 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 . */ #define _GNU_SOURCE #include "smartdns/tlog.h" #include "smartdns/util.h" #include #include #include #include void bug_ext(const char *file, int line, const char *func, const char *errfmt, ...) { va_list ap; va_start(ap, errfmt); tlog_vext(TLOG_FATAL, file, line, func, NULL, errfmt, ap); va_end(ap); print_stack(); /* trigger BUG */ sleep(1); raise(SIGSEGV); while (1) { sleep(1); }; } #ifdef HAVE_UNWIND_BACKTRACE #include struct backtrace_state { void **current; void **end; }; static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context *context, void *arg) { struct backtrace_state *state = (struct backtrace_state *)(arg); uintptr_t pc = _Unwind_GetIP(context); if (pc) { if (state->current == state->end) { return _URC_END_OF_STACK; } *state->current++ = (void *)(pc); } return _URC_NO_REASON; } void print_stack(void) { const size_t max_buffer = 30; void *buffer[max_buffer]; int idx = 0; struct backtrace_state state = {buffer, buffer + max_buffer}; _Unwind_Backtrace(unwind_callback, &state); int frame_num = state.current - buffer; if (frame_num == 0) { return; } tlog(TLOG_FATAL, "Stack:"); for (idx = 0; idx < frame_num; ++idx) { const void *addr = buffer[idx]; const char *symbol = ""; Dl_info info; memset(&info, 0, sizeof(info)); if (dladdr(addr, &info) && info.dli_sname) { symbol = info.dli_sname; } void *offset = (void *)((char *)(addr) - (char *)(info.dli_fbase)); tlog(TLOG_FATAL, "#%.2d: %p %s() from %s+%p", idx + 1, addr, symbol, info.dli_fname, offset); } } #else void print_stack(void) {} #endif ================================================ FILE: src/utils/tls_header_parse.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/util.h" #define SERVER_NAME_LEN 256 #define TLS_HEADER_LEN 5 #define TLS_HANDSHAKE_CONTENT_TYPE 0x16 #define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01 #ifndef MIN #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) #endif static int parse_extensions(const char *, size_t, char *, const char **); static int parse_server_name_extension(const char *, size_t, char *, const char **); /* Parse a TLS packet for the Server Name Indication extension in the client * hello handshake, returning the first server name found (pointer to static * array) * * Returns: * >=0 - length of the hostname and updates *hostname * caller is responsible for freeing *hostname * -1 - Incomplete request * -2 - No Host header included in this request * -3 - Invalid hostname pointer * -4 - malloc failure * < -4 - Invalid TLS client hello */ int parse_tls_header(const char *data, size_t data_len, char *hostname, const char **hostname_ptr) { char tls_content_type = 0; char tls_version_major = 0; char tls_version_minor = 0; size_t pos = TLS_HEADER_LEN; size_t len = 0; if (hostname == NULL) { return -3; } /* Check that our TCP payload is at least large enough for a TLS header */ if (data_len < TLS_HEADER_LEN) { return -1; } /* SSL 2.0 compatible Client Hello * * High bit of first byte (length) and content type is Client Hello * * See RFC5246 Appendix E.2 */ if (data[0] & 0x80 && data[2] == 1) { return -2; } tls_content_type = data[0]; if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { return -5; } tls_version_major = data[1]; tls_version_minor = data[2]; if (tls_version_major < 3) { return -2; } /* TLS record length */ len = ((unsigned char)data[3] << 8) + (unsigned char)data[4] + TLS_HEADER_LEN; data_len = MIN(data_len, len); /* Check we received entire TLS record length */ if (data_len < len) { return -1; } /* * Handshake */ if (pos + 1 > data_len) { return -5; } if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) { return -5; } /* Skip past fixed length records: * 1 Handshake Type * 3 Length * 2 Version (again) * 32 Random * to Session ID Length */ pos += 38; /* Session ID */ if (pos + 1 > data_len) { return -5; } len = (unsigned char)data[pos]; pos += 1 + len; /* Cipher Suites */ if (pos + 2 > data_len) { return -5; } len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; pos += 2 + len; /* Compression Methods */ if (pos + 1 > data_len) { return -5; } len = (unsigned char)data[pos]; pos += 1 + len; if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) { return -2; } /* Extensions */ if (pos + 2 > data_len) { return -5; } len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; pos += 2; if (pos + len > data_len) { return -5; } return parse_extensions(data + pos, len, hostname, hostname_ptr); } static int parse_extensions(const char *data, size_t data_len, char *hostname, const char **hostname_ptr) { size_t pos = 0; size_t len = 0; /* Parse each 4 bytes for the extension header */ while (pos + 4 <= data_len) { /* Extension Length */ len = ((unsigned char)data[pos + 2] << 8) + (unsigned char)data[pos + 3]; /* Check if it's a server name extension */ if (data[pos] == 0x00 && data[pos + 1] == 0x00) { /* There can be only one extension of each type, so we break * our state and move p to beginning of the extension here */ if (pos + 4 + len > data_len) { return -5; } return parse_server_name_extension(data + pos + 4, len, hostname, hostname_ptr); } pos += 4 + len; /* Advance to the next extension header */ } /* Check we ended where we expected to */ if (pos != data_len) { return -5; } return -2; } static int parse_server_name_extension(const char *data, size_t data_len, char *hostname, const char **hostname_ptr) { size_t pos = 2; /* skip server name list length */ size_t len = 0; while (pos + 3 < data_len) { len = ((unsigned char)data[pos + 1] << 8) + (unsigned char)data[pos + 2]; if (pos + 3 + len > data_len) { return -5; } switch (data[pos]) { /* name type */ case 0x00: /* host_name */ strncpy(hostname, data + pos + 3, len); if (hostname_ptr) { *hostname_ptr = data + pos + 3; } hostname[len] = '\0'; return len; default: break; } pos += 3 + len; } /* Check we ended where we expected to */ if (pos != data_len) { return -5; } return -2; } ================================================ FILE: src/utils/url.c ================================================ /************************************************************************* * * 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 . */ #include "smartdns/util.h" #include #include #include int parse_uri(const char *value, char *scheme, char *host, int *port, char *path) { return parse_uri_ext(value, scheme, NULL, NULL, host, port, path); } int urldecode(char *dst, int dst_maxlen, const char *src) { char a, b; int len = 0; while (*src) { if ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) { if (a >= 'a') { a -= 'a' - 'A'; } if (a >= 'A') { a -= ('A' - 10); } else { a -= '0'; } if (b >= 'a') { b -= 'a' - 'A'; } if (b >= 'A') { b -= ('A' - 10); } else { b -= '0'; } *dst++ = 16 * a + b; src += 3; } else if (*src == '+') { *dst++ = ' '; src++; } else { *dst++ = *src++; } len++; if (len >= dst_maxlen - 1) { return -1; } } *dst++ = '\0'; return len; } int parse_uri_ext(const char *value, char *scheme, char *user, char *password, char *host, int *port, char *path) { char *scheme_end = NULL; int field_len = 0; const char *process_ptr = value; char user_pass_host_part[PATH_MAX]; char *user_password = NULL; char *host_part = NULL; const char *host_end = NULL; scheme_end = strstr(value, "://"); if (scheme_end) { field_len = scheme_end - value; if (scheme) { memcpy(scheme, value, field_len); scheme[field_len] = 0; } process_ptr += field_len + 3; } else { if (scheme) { scheme[0] = '\0'; } } host_end = strstr(process_ptr, "/"); if (host_end == NULL) { host_end = process_ptr + strlen(process_ptr); }; field_len = host_end - process_ptr; if (field_len >= (int)sizeof(user_pass_host_part)) { return -1; } memcpy(user_pass_host_part, process_ptr, field_len); user_pass_host_part[field_len] = 0; host_part = strstr(user_pass_host_part, "@"); if (host_part != NULL) { *host_part = '\0'; host_part = host_part + 1; user_password = user_pass_host_part; char *sep = strstr(user_password, ":"); if (sep != NULL) { *sep = '\0'; sep = sep + 1; if (password) { if (urldecode(password, 128, sep) < 0) { return -1; } } } if (user) { if (urldecode(user, 128, user_password) < 0) { return -1; } } } else { host_part = user_pass_host_part; } if (host != NULL && parse_ip(host_part, host, port) != 0) { return -1; } process_ptr += field_len; if (path) { strcpy(path, process_ptr); } return 0; } int parse_ip(const char *value, char *ip, int *port) { int offset = 0; char *colon = NULL; colon = strstr(value, ":"); if (strstr(value, "[")) { /* ipv6 with port */ char *bracket_end = strstr(value, "]"); if (bracket_end == NULL) { return -1; } offset = bracket_end - value - 1; memcpy(ip, value + 1, offset); ip[offset] = 0; colon = strstr(bracket_end, ":"); if (colon) { colon++; } } else if (colon && strstr(colon + 1, ":")) { /* ipv6 without port */ strncpy(ip, value, MAX_IP_LEN); colon = NULL; } else { /* ipv4 */ colon = strstr(value, ":"); if (colon == NULL) { /* without port */ strncpy(ip, value, MAX_IP_LEN); } else { /* with port */ offset = colon - value; colon++; memcpy(ip, value, offset); ip[offset] = 0; } } if (colon) { /* get port num */ *port = atoi(colon); } else { *port = PORT_NOT_DEFINED; } if (ip[0] == 0) { return -1; } return 0; } ================================================ FILE: systemd/smartdns.service.in ================================================ [Unit] Description=SmartDNS Server After=network.target Before=network-online.target Before=nss-lookup.target Wants=nss-lookup.target StartLimitBurst=0 StartLimitIntervalSec=60 [Service] Type=forking PIDFile=@RUNSTATEDIR@/smartdns.pid EnvironmentFile=@SYSCONFDIR@/default/smartdns ExecStart=@SBINDIR@/smartdns -p @RUNSTATEDIR@/smartdns.pid $SMART_DNS_OPTS Restart=always RestartSec=2 TimeoutStopSec=15 [Install] WantedBy=multi-user.target Alias=smartdns.service ================================================ FILE: test/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 . SMARTDNS_SRC_DIR=../src BIN=test.bin SMARTDNS_TEST_LIB=$(SMARTDNS_SRC_DIR)/libsmartdns-test.a CXXFLAGS += -g CXXFLAGS += -DTEST CXXFLAGS += -I./ -I../src -I../src/include CXXFLAGS += -fno-omit-frame-pointer -Wstrict-aliasing -funwind-tables TEST_SOURCES := $(wildcard *.cc) $(wildcard */*.cc) $(wildcard */*/*.cc) TEST_OBJECTS := $(patsubst %.cc, %.o, $(TEST_SOURCES)) OBJS += $(TEST_OBJECTS) SMARTDNS_SRC_FILES := $(wildcard $(SMARTDNS_SRC_DIR)/*.c) $(wildcard $(SMARTDNS_SRC_DIR)/*.h) $(wildcard $(SMARTDNS_SRC_DIR)/lib/*.c) ifeq ($(filter -j,$(MAKEFLAGS)),) MAKEFLAGS += -j$(shell nproc) endif LDFLAGS += -lssl -lcrypto -lpthread -ldl -lgtest -lstdc++ -lm -rdynamic LDFLAGS += $(EXTRA_LDFLAGS) .PHONY: all clean test $(SMARTDNS_TEST_LIB) all: $(BIN) $(BIN) : $(OBJS) $(SMARTDNS_TEST_LIB) $(CC) $^ -o $@ $(LDFLAGS) test: $(BIN) ./$(BIN) $(SMARTDNS_TEST_LIB): $(MAKE) DEBUG=1 -C $(SMARTDNS_SRC_DIR) libsmartdns-test.a clean: $(RM) $(OBJS) $(BIN) $(MAKE) -C $(SMARTDNS_SRC_DIR) clean ================================================ FILE: test/cases/test-address.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "include/utils.h" #include "server.h" #include "smartdns/dns.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class Address : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(Address, soa) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/#4 address /b.com/#6 address /c.com/# )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); EXPECT_EQ(client.GetAuthority()[0].GetData(), "a.gtld-servers.net. nstld.verisign-grs.com. 1800 1800 900 604800 86400"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); ASSERT_TRUE(client.Query("b.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("b.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); EXPECT_EQ(client.GetAuthority()[0].GetData(), "a.gtld-servers.net. nstld.verisign-grs.com. 1800 1800 900 604800 86400"); ASSERT_TRUE(client.Query("c.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "c.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); EXPECT_EQ(client.GetAuthority()[0].GetData(), "a.gtld-servers.net. nstld.verisign-grs.com. 1800 1800 900 604800 86400"); ASSERT_TRUE(client.Query("c.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "c.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); EXPECT_EQ(client.GetAuthority()[0].GetData(), "a.gtld-servers.net. nstld.verisign-grs.com. 1800 1800 900 604800 86400"); } TEST_F(Address, ip) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/10.10.10.10 address /a.com/64:ff9b::1010:1010 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "10.10.10.10"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::1010:1010"); } TEST_F(Address, multiaddress) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/10.10.10.10,11.11.11.11,22.22.22.22 address /a.com/64:ff9b::1010:1010,64:ff9b::1111:1111,64:ff9b::2222:2222 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; std::map result; ASSERT_EQ(client.GetAnswerNum(), 3); EXPECT_EQ(client.GetStatus(), "NOERROR"); auto answers = client.GetAnswer(); for (int i = 0; i < client.GetAnswerNum(); i++) { result[client.GetAnswer()[i].GetData()] = &answers[i]; } ASSERT_NE(result.find("10.10.10.10"), result.end()); auto check_result = result["10.10.10.10"]; EXPECT_EQ(check_result->GetName(), "a.com"); EXPECT_EQ(check_result->GetTTL(), 600); EXPECT_EQ(check_result->GetType(), "A"); EXPECT_EQ(check_result->GetData(), "10.10.10.10"); ASSERT_NE(result.find("11.11.11.11"), result.end()); check_result = result["11.11.11.11"]; EXPECT_EQ(check_result->GetName(), "a.com"); EXPECT_EQ(check_result->GetTTL(), 600); EXPECT_EQ(check_result->GetType(), "A"); EXPECT_EQ(check_result->GetData(), "11.11.11.11"); ASSERT_NE(result.find("22.22.22.22"), result.end()); check_result = result["22.22.22.22"]; EXPECT_EQ(check_result->GetName(), "a.com"); EXPECT_EQ(check_result->GetTTL(), 600); EXPECT_EQ(check_result->GetType(), "A"); EXPECT_EQ(check_result->GetData(), "22.22.22.22"); result.clear(); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 3); EXPECT_EQ(client.GetStatus(), "NOERROR"); answers = client.GetAnswer(); for (int i = 0; i < client.GetAnswerNum(); i++) { result[client.GetAnswer()[i].GetData()] = &answers[i]; } ASSERT_NE(result.find("64:ff9b::1010:1010"), result.end()); check_result = result["64:ff9b::1010:1010"]; EXPECT_EQ(check_result->GetName(), "a.com"); EXPECT_EQ(check_result->GetTTL(), 600); EXPECT_EQ(check_result->GetType(), "AAAA"); EXPECT_EQ(check_result->GetData(), "64:ff9b::1010:1010"); ASSERT_NE(result.find("64:ff9b::1111:1111"), result.end()); check_result = result["64:ff9b::1111:1111"]; EXPECT_EQ(check_result->GetName(), "a.com"); EXPECT_EQ(check_result->GetTTL(), 600); EXPECT_EQ(check_result->GetType(), "AAAA"); EXPECT_EQ(check_result->GetData(), "64:ff9b::1111:1111"); ASSERT_NE(result.find("64:ff9b::2222:2222"), result.end()); check_result = result["64:ff9b::2222:2222"]; EXPECT_EQ(check_result->GetName(), "a.com"); EXPECT_EQ(check_result->GetTTL(), 600); EXPECT_EQ(check_result->GetType(), "AAAA"); EXPECT_EQ(check_result->GetData(), "64:ff9b::2222:2222"); } TEST_F(Address, soa_sub_ip) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/192.168.1.1 address /com/# )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "192.168.1.1"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("b.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); } TEST_F(Address, set_ipv4_query_ipv6) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/192.168.1.1 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "192.168.1.1"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); } TEST_F(Address, set_ipv6_query_ipv4) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/64:ff9b::1010:1010 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::1010:1010"); } TEST_F(Address, set_ipv4_allow_ipv6) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/192.168.1.1 address /b.a.com/-6 address /com/# )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "192.168.1.1"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("c.a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("b.a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); ASSERT_TRUE(client.Query("b.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); } TEST_F(Address, set_both_ipv4_ipv6) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/192.168.1.1 address /b.a.com/64:ff9b::1010:1010 address /com/# )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "192.168.1.1"); ASSERT_TRUE(client.Query("c.a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("b.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "192.168.1.1"); ASSERT_TRUE(client.Query("b.a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::1010:1010"); ASSERT_TRUE(client.Query("b.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); } TEST_F(Address, ipv4_in_Test) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/::ffff:192.168.1.1 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "::ffff:192.168.1.1"); ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); } TEST_F(Address, group_ignore_address) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 bind [::]:60054 -group ignore-address server 127.0.0.1:61053 address 2.3.4.5 speed-check-mode none address /a.com/2.3.4.6 group-begin ignore-address address - group-end )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2.3.4.6"); ASSERT_TRUE(client.Query("b.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2.3.4.5"); ASSERT_TRUE(client.Query("b.com A", 60054)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-audit.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "include/utils.h" #include "server.h" #include "smartdns/dns.h" #include "gtest/gtest.h" class Audit : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(Audit, log) { smartdns::MockServer server_upstream; smartdns::Server server; Defer { unlink("/tmp/audit.log"); }; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 audit-file /tmp/audit.log audit-enable yes server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); std::ifstream audit_file("/tmp/audit.log"); std::string line; bool found = false; while (std::getline(audit_file, line)) { if (line.find("a.com") != std::string::npos) { found = true; break; } } audit_file.close(); ASSERT_TRUE(found) << "Audit log for a.com not found"; } ================================================ FILE: test/cases/test-bind.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" #include #include #include #include #include TEST(Bind, tls) { Defer { unlink("/tmp/smartdns-cert.pem"); unlink("/tmp/smartdns-key.pem"); }; smartdns::Server server_wrap; smartdns::Server server; server.Start(R"""(bind [::]:61053 server-tls 127.0.0.1:60053 -no-check-certificate )"""); server_wrap.Start(R"""(bind-tls [::]:60053 address /example.com/1.2.3.4 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("example.com", 61053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST(Bind, https) { Defer { unlink("/tmp/smartdns-cert.pem"); unlink("/tmp/smartdns-key.pem"); }; smartdns::Server server_wrap; smartdns::Server server; server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053 -no-check-certificate )"""); server_wrap.Start(R"""(bind-https [::]:60053 address /example.com/1.2.3.4 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("example.com", 61053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST(Bind, udp_tcp) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { unsigned char addr[4] = {1, 2, 3, 4}; dns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""( bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com +tcp", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_GE(client.GetAnswer()[0].GetTTL(), 609); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST(Bind, self) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""( bind [::]:60053 -group self server 127.0.0.1:61053 -group self bind [::]:61053 -group upstream server 127.0.0.1:62053 -group upstream )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 100); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST(Bind, nocache) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { unsigned char addr[4] = {1, 2, 3, 4}; usleep(15 * 1000); dns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""( bind [::]:60053 --no-cache bind-tcp [::]:60053 server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 611); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com", 60053)); EXPECT_GT(client.GetQueryTime(), 10); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 611); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST(Bind, device) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""( bind [::]:60053@lo server 127.0.0.1:62053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 100); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST(Bind, malformed_packet) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""( bind [::]:60053@lo server 127.0.0.1:62053 log-level info )"""); int sockfd = socket(AF_INET, SOCK_DGRAM, 0); ASSERT_NE(sockfd, -1); Defer { close(sockfd); }; struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(60053); server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); for (int i = 0; i < 100000; i++) { char buf[4096]; int len = random() % 4096; if (len <= 0) { len = 1; } RAND_bytes((unsigned char *)buf, len); sendto(sockfd, buf, len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)); } smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 100); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST(Bind, group) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "9.10.11.12", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream1.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:63053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8", 611); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 bind [::]:60153 -group g1 bind [::]:60253 -group g2 server 127.0.0.1:61053 server 127.0.0.1:62053 -group g1 -exclude-default-group server 127.0.0.1:63053 -group g2 -exclude-default-group )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "9.10.11.12"); ASSERT_TRUE(client.Query("a.com", 60153)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com", 60253)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); } TEST(Bind, Get) { smartdns::Server server; int ret = system("which curl > /dev/null 2>&1"); if (ret != 0) { GTEST_SKIP() << "curl not found, skip test"; } // Start server with bind-https and logging to file server.Start(R"""(bind-https [::]:60053 -alpn h2 address /example.com/1.2.3.4 log-level debug )"""); // Send GET request using curl std::string cmd = "curl -k --http2 'https://127.0.0.1:60053/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=' > /dev/null 2>&1"; ret = system(cmd.c_str()); ASSERT_EQ(ret, 0); } ================================================ FILE: test/cases/test-bootstrap.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" class BootStrap : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(BootStrap, bootstrap) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "127.0.0.1", 611); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:62053 -bootstrap-dns server udp://example.com:61053 -group test )"""); smartdns::Client client; usleep(2500000); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-cache.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" #include #include #include #include /* clang-format off */ #include "smartdns/dns_cache.h" /* clang-format on */ class Cache : public ::testing::Test { protected: void SetUp() override {} void TearDown() override {} }; TEST_F(Cache, min) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[4] = {1, 2, 3, 4}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 cache-size 1 rr-ttl-min 1 speed-check-mode none response-mode fastest-response )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 1); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Cache, max_reply_ttl) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[4] = {1, 2, 3, 4}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 cache-size 1 rr-ttl-min 600 rr-ttl-reply-max 5 speed-check-mode none response-mode fastest-response )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 5); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); sleep(1); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 5); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Cache, max_reply_ttl_expired) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[4] = {1, 2, 3, 4}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 cache-size 1 rr-ttl-min 600 rr-ttl-reply-max 6 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GE(client.GetAnswer()[0].GetTTL(), 5); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Cache, prefetch) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "9.10.11.12", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream1.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:63053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "9.10.11.12", 60, 110); server.Start(R"""(bind [::]:60053 bind [::]:60153 -group g1 server 127.0.0.1:61053 server 127.0.0.1:62053 -group g1 -exclude-default-group server 127.0.0.1:63053 -group g2 prefetch-domain yes rr-ttl-max 2 serve-expired no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); ASSERT_TRUE(client.Query("a.com", 60153)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); sleep(1); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); sleep(1); ASSERT_TRUE(client.Query("a.com", 60153)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Cache, nocache) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } usleep(15000); if (request->qtype == DNS_T_A) { unsigned char addr[4] = {1, 2, 3, 4}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 cache-size 100 rr-ttl-min 600 rr-ttl-reply-max 5 domain-rules /a.com/ --no-cache )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 5); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com", 60053)); EXPECT_GT(client.GetQueryTime(), 10); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 5); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Cache, save_file) { smartdns::MockServer server_upstream; auto cache_file = "/tmp/smartdns_cache." + smartdns::GenerateRandomString(10); std::string conf = R"""( bind [::]:60053@lo server 127.0.0.1:62053 cache-persist yes dualstack-ip-selection no )"""; conf += "cache-file " + cache_file; Defer { unlink(cache_file.c_str()); }; server_upstream.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); { smartdns::Server server; server.Start(conf); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 100); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); server.Stop(); usleep(200 * 1000); } ASSERT_EQ(access(cache_file.c_str(), F_OK), 0); std::fstream fs(cache_file, std::ios::in); struct dns_cache_file head; memset(&head, 0, sizeof(head)); fs.read((char *)&head, sizeof(head)); EXPECT_EQ(head.magic, MAGIC_NUMBER); EXPECT_EQ(head.cache_number, 1); } TEST_F(Cache, corrupt_file) { smartdns::MockServer server_upstream; auto cache_file = "/tmp/smartdns_cache." + smartdns::GenerateRandomString(10); std::string conf = R"""( bind [::]:60053@lo server 127.0.0.1:62053 dualstack-ip-selection no cache-persist yes )"""; conf += "cache-file " + cache_file; Defer { unlink(cache_file.c_str()); }; server_upstream.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); { smartdns::Server server; server.Start(conf); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 100); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); server.Stop(); usleep(200 * 1000); } ASSERT_EQ(access(cache_file.c_str(), F_OK), 0); int fd = open(cache_file.c_str(), O_RDWR); ASSERT_NE(fd, -1); srandom(time(NULL)); off_t file_size = lseek(fd, 0, SEEK_END); off_t offset = random() % (file_size - 300); std::cout << "try make corrupt at " << offset << ", file size: " << file_size << std::endl; lseek(fd, offset, SEEK_SET); for (int i = 0; i < 300; i++) { unsigned char c = random() % 256; write(fd, &c, 1); } close(fd); { smartdns::Server server; server.Start(conf); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 100); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); server.Stop(); usleep(200 * 1000); } } TEST_F(Cache, cname) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; std::string cname = "cname." + domain; if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } unsigned char addr[4] = {1, 2, 3, 4}; dns_add_domain(request->response_packet, domain.c_str(), DNS_T_A, DNS_C_IN); dns_add_CNAME(request->response_packet, DNS_RRS_AN, domain.c_str(), 300, cname.c_str()); dns_add_A(request->response_packet, DNS_RRS_AN, cname.c_str(), 300, addr); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 cache-size 100 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GE(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "cname.a.com."); EXPECT_EQ(client.GetAnswer()[1].GetName(), "cname.a.com"); EXPECT_GE(client.GetAnswer()[1].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[1].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("cname.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "cname.a.com"); EXPECT_GE(client.GetAnswer()[0].GetTTL(), 590); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-client-rule.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" class ClientRule : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(ClientRule, rule) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -g client -e server udp://127.0.0.1:62053 client-rules 127.0.0.1 -g client )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(ClientRule, group_begin_group_end) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:62053 group-begin client server udp://127.0.0.1:61053 -e client-rules 127.0.0.1 group-end )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(ClientRule, acl) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); auto ipaddr = smartdns::GetAvailableIPAddresses(); if (ipaddr.size() < 0) { return; } std::string confstr = R"""(bind [::]:60053 server 127.0.0.1:61053 acl-enable yes )"""; confstr += "client-rules " + ipaddr[0] + "\n"; server.Start(confstr); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; EXPECT_EQ(client.GetStatus(), "REFUSED"); ASSERT_EQ(client.GetAnswerNum(), 0); ASSERT_TRUE(client.Query("b.com", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(ClientRule, in_group_nameserver) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::MockServer server_upstream3; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream3.Start("udp://0.0.0.0:63053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } usleep(10000); smartdns::MockServer::AddIP(request, request->domain.c_str(), "7.8.9.10", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.MockPing(PING_TYPE_ICMP, "4.5.6.7", 60, 10); server.MockPing(PING_TYPE_ICMP, "7.8.9.10", 60, 10); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:62053 server udp://127.0.0.1:63053 -g in_group group-begin client server udp://127.0.0.1:61053 -e client-rules 127.0.0.1 nameserver /cn/in_group group-end )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("b.cn", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.cn"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "7.8.9.10"); } ================================================ FILE: test/cases/test-cname.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" class Cname : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(Cname, cname) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); EXPECT_EQ(request->domain, "e.com"); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 cname /a.com/b.com cname /b.com/c.com cname /c.com/d.com cname /d.com/e.com server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "b.com."); EXPECT_EQ(client.GetAnswer()[1].GetData(), "1.2.3.4"); } TEST_F(Cname, subdomain1) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } if (request->domain == "s.a.com") { smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 700); return smartdns::SERVER_REQUEST_OK; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 cname /a.com/s.a.com server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "s.a.com."); EXPECT_EQ(client.GetAnswer()[1].GetData(), "4.5.6.7"); } TEST_F(Cname, subdomain2) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } if (request->domain == "a.s.a.com") { smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 700); return smartdns::SERVER_REQUEST_OK; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 cname /a.com/s.a.com cname /s.a.com/a.s.a.com server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "s.a.com."); EXPECT_EQ(client.GetAnswer()[1].GetData(), "4.5.6.7"); } TEST_F(Cname, loop) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } if (request->domain == "s.a.com") { smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 700); return smartdns::SERVER_REQUEST_OK; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 cname /a.com/c.a.com cname /c.a.com/s.a.com server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "c.a.com."); EXPECT_EQ(client.GetAnswer()[1].GetData(), "4.5.6.7"); } TEST_F(Cname, query_cname) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } if (request->domain == "s.a.com") { smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 700); return smartdns::SERVER_REQUEST_OK; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 cname /a.com/s.a.com server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("s.a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "s.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "4.5.6.7"); } ================================================ FILE: test/cases/test-ddns.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class DDNS : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(DDNS, smartdns) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("smartdns A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "smartdns"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "127.0.0.1"); ASSERT_TRUE(client.Query("smartdns AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "smartdns"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "::1"); } TEST_F(DDNS, ddns) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 ddns-domain test.ddns.com ddns-domain test.ddns.org dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("test.ddns.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "test.ddns.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "127.0.0.1"); ASSERT_TRUE(client.Query("test.ddns.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "test.ddns.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "::1"); ASSERT_TRUE(client.Query("test.ddns.org A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "test.ddns.org"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "127.0.0.1"); ASSERT_TRUE(client.Query("test.ddns.org AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "test.ddns.org"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "::1"); } ================================================ FILE: test/cases/test-ddr.cc ================================================ #include "client.h" #include "include/utils.h" #include "server.h" #include "smartdns/dns.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class DDR : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(DDR, DDR_RESPONSE) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 -ddr bind-tls [::]:60153 -ddr bind-https [::]:60253 -ddr -alpn h2 bind-tcp [::]:60353 server 127.0.0.1:61053 log-console yes log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("_dns.resolver.arpa SVCB", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); bool has_doq = false; bool has_dot = false; for (auto &ans : client.GetAnswer()) { EXPECT_EQ(ans.GetName(), "_dns.resolver.arpa"); EXPECT_EQ(ans.GetType(), "SVCB"); if (ans.GetData().find("alpn=\"h2\"") != std::string::npos) { has_doq = true; } if (ans.GetData().find("alpn=\"dot\"") != std::string::npos) { has_dot = true; } } EXPECT_TRUE(has_doq); EXPECT_TRUE(has_dot); } TEST_F(DDR, DDR_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 bind-tls [::]:60053 server 127.0.0.1:61053 log-console yes log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("_dns.resolver.arpa SVCB", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "_dns.resolver.arpa"); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } ================================================ FILE: test/cases/test-discard-block-ip.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" TEST(DiscardBlockIP, first_ping) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream1.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } unsigned char addr[4] = {0, 0, 0, 0}; dns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } unsigned char addr[4] = {1, 2, 3, 4}; usleep(20000); dns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 server 127.0.0.1:62053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 100); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST(DiscardBlockIP, first_response) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream1.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { unsigned char addr[4] = {0, 0, 0, 0}; dns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { unsigned char addr[4] = {1, 2, 3, 4}; usleep(20000); dns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 server 127.0.0.1:62053 response-mode fastest-response )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-dns64.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "include/utils.h" #include "server.h" #include "smartdns/dns.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class DNS64 : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(DNS64, no_dualstack) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dns64 64:ff9b::/96 dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); } TEST_F(DNS64, with_dualstack) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 200); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dns64 64:ff9b::/96 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); EXPECT_LT(client.GetQueryTime(), 100); usleep(500000); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 500); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); EXPECT_LT(client.GetQueryTime(), 100); } TEST_F(DNS64, with_AAAA_result) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 300); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 90); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 100); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dns64 64:ff9b::/96 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 1200); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); } TEST_F(DNS64, ipv4_in_ipv6) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "::ffff:1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "::ffff:1.2.3.4", 60, 10); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 90); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 1200); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "::ffff:1.2.3.4"); } TEST_F(DNS64, ipv4only_arpa) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "::ffff:1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dns64 64:ff9b::/96 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("ipv4only.arpa AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("ipv4only.arpa A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "ipv4only.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "192.0.0.170"); EXPECT_EQ(client.GetAnswer()[1].GetName(), "ipv4only.arpa"); EXPECT_EQ(client.GetAnswer()[1].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[1].GetData(), "192.0.0.171"); } TEST_F(DNS64, ipv4only_arpa_nodns64) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "::ffff:1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "::ffff:1.2.3.4", 60, 10); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 90); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 1200); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 590); EXPECT_EQ(client.GetAnswer()[0].GetData(), "::ffff:1.2.3.4"); ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 1200); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 590); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-domain-rule.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" class DomainRule : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(DomainRule, bogus_nxdomain) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } if (request->domain == "a.com") { smartdns::MockServer::AddIP(request, request->domain.c_str(), "10.11.12.13", 611); return smartdns::SERVER_REQUEST_OK; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -blacklist-ip bogus-nxdomain 10.0.0.0/8 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-domain-set.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class DomainSet : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(DomainSet, set_add) { smartdns::MockServer server_upstream; smartdns::Server server; smartdns::TempFile file_set; std::vector domain_list; int count = 16; std::string config = "domain-set -name test-set -file " + file_set.GetPath() + "\n"; config += R"""(bind [::]:60053 server 127.0.0.1:61053 domain-rules /domain-set:test-set/ -c none --dualstack-ip-selection no -a 9.9.9.9 )"""; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); for (int i = 0; i < count; i++) { auto domain = smartdns::GenerateRandomString(10) + "." + smartdns::GenerateRandomString(3); file_set.Write(domain); file_set.Write("\n"); domain_list.emplace_back(domain); } std::cout << config << std::endl; server.Start(config); smartdns::Client client; for (auto &domain : domain_list) { ASSERT_TRUE(client.Query(domain, 60053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), domain); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "9.9.9.9"); } ASSERT_TRUE(client.Query("a.com", 60053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-dualstack.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class DualStack : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(DualStack, ipv4_prefer) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 150); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 150); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); usleep(220 * 1000); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8"); } TEST_F(DualStack, ipv6_prefer_allow_force_AAAA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 70); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 75); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping dualstack-ip-allow-force-AAAA yes )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); usleep(220 * 1000); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "2001:db8::2"); } TEST_F(DualStack, ipv6_prefer_without_ipv4) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 70); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 100); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping dualstack-ip-allow-force-AAAA yes )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); usleep(220 * 1000); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); } TEST_F(DualStack, ipv6_no_speed) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 10000); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 10000); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); usleep(220 * 1000); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_GT(client.GetAuthority()[0].GetTTL(), 597); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } TEST_F(DualStack, ipv4_no_response) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_NO_RESPONSE; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10000); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 10000); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 100); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 110); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection yes speed-check-mode ping )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "SERVFAIL"); usleep(220 * 1000); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 590); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); EXPECT_EQ(client.GetAnswer()[1].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[1].GetTTL(), 590); EXPECT_EQ(client.GetAnswer()[1].GetData(), "2001:db8::2"); } ================================================ FILE: test/cases/test-edns.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class EDNS : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(EDNS, client) { smartdns::MockServer server_upstream; smartdns::Server server; struct dns_opt_ecs ecs; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { int rr_count = 0; int i = 0; int ret = 0; struct dns_rrs *rrs = NULL; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count > 0) { for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { switch (rrs->type) { case DNS_OPT_T_ECS: { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } dns_add_OPT_ECS(request->response_packet, &ecs); } break; default: break; } } } if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A +subnet=2.2.2.2/24", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_EQ(client.GetOpt().size(), 2); EXPECT_EQ(client.GetOpt()[1], "CLIENT-SUBNET: 2.2.2.0/24/0"); EXPECT_EQ(ecs.family, 1); EXPECT_EQ(ecs.source_prefix, 24); EXPECT_EQ(ecs.scope_prefix, 0); unsigned char edns_addr[4] = {2, 2, 2, 0}; EXPECT_EQ(memcmp(ecs.addr, &edns_addr, 4), 0); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(EDNS, server) { smartdns::MockServer server_upstream; smartdns::Server server; struct dns_opt_ecs ecs; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { int rr_count = 0; int i = 0; int ret = 0; struct dns_rrs *rrs = NULL; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count > 0) { for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { switch (rrs->type) { case DNS_OPT_T_ECS: { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } dns_add_OPT_ECS(request->response_packet, &ecs); } break; default: break; } } } if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 -subnet=2.2.2.0/24 speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(ecs.family, 1); EXPECT_EQ(ecs.source_prefix, 24); EXPECT_EQ(ecs.scope_prefix, 0); unsigned char edns_addr[4] = {2, 2, 2, 0}; EXPECT_EQ(memcmp(ecs.addr, &edns_addr, 4), 0); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(EDNS, server_v6) { smartdns::MockServer server_upstream; smartdns::Server server; struct dns_opt_ecs ecs; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { int rr_count = 0; int i = 0; int ret = 0; struct dns_rrs *rrs = NULL; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count > 0) { for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { switch (rrs->type) { case DNS_OPT_T_ECS: { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } dns_add_OPT_ECS(request->response_packet, &ecs); } break; default: break; } } } if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 -subnet=64:ff9b::/96 speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(ecs.family, 2); EXPECT_EQ(ecs.source_prefix, 96); EXPECT_EQ(ecs.scope_prefix, 0); unsigned char edns_addr[16] = {00, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT_EQ(memcmp(ecs.addr, &edns_addr, 16), 0); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(EDNS, edns_client_subnet) { smartdns::MockServer server_upstream; smartdns::Server server; struct dns_opt_ecs ecs; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { int rr_count = 0; int i = 0; int ret = 0; struct dns_rrs *rrs = NULL; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count > 0) { for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { switch (rrs->type) { case DNS_OPT_T_ECS: { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } dns_add_OPT_ECS(request->response_packet, &ecs); } break; default: break; } } } if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none edns-client-subnet 2.2.2.2/24 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(ecs.family, 1); EXPECT_EQ(ecs.source_prefix, 24); EXPECT_EQ(ecs.scope_prefix, 0); unsigned char edns_addr[4] = {2, 2, 2, 0}; EXPECT_EQ(memcmp(ecs.addr, &edns_addr, 4), 0); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(EDNS, edns_client_subnet_v6) { smartdns::MockServer server_upstream; smartdns::Server server; struct dns_opt_ecs ecs; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { int rr_count = 0; int i = 0; int ret = 0; struct dns_rrs *rrs = NULL; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count > 0) { for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { switch (rrs->type) { case DNS_OPT_T_ECS: { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } dns_add_OPT_ECS(request->response_packet, &ecs); } break; default: break; } } } if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none edns-client-subnet 64:ff9b::/96 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(ecs.family, 2); EXPECT_EQ(ecs.source_prefix, 96); EXPECT_EQ(ecs.scope_prefix, 0); unsigned char edns_addr[16] = {00, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; EXPECT_EQ(memcmp(ecs.addr, &edns_addr, 16), 0); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-group.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" class Group : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(Group, conf_file) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; std::string file = "/tmp/smartdns_conf_file" + smartdns::GenerateRandomString(5) + ".conf"; std::ofstream ofs(file); ASSERT_TRUE(ofs.is_open()); Defer { ofs.close(); unlink(file.c_str()); }; ofs << R"""( server udp://127.0.0.1:61053 -e client-rules 127.0.0.1 address /a.com/1.1.1.1 domain-rules /b.com/ -address 4.5.6.7 # should pop all groups group-begin dummy address /a.com/9.9.9.9 )"""; ofs.flush(); server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.Start(R"""(bind [::]:60053 conf-file /tmp/smartdns_conf_file*.conf -g client server udp://127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1"); ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "4.5.6.7"); auto ipaddr = smartdns::GetAvailableIPAddresses(); if (ipaddr.size() > 0) { ASSERT_TRUE(client.Query("a.com", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } } TEST_F(Group, conf_file_ip_rule) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; std::string file = "/tmp/smartdns_conf_file" + smartdns::GenerateRandomString(5) + ".conf"; std::ofstream ofs(file); ASSERT_TRUE(ofs.is_open()); Defer { ofs.close(); unlink(file.c_str()); }; ofs << R"""( server udp://127.0.0.1:61053 -e client-rules 127.0.0.1 ignore-ip 7.8.9.10 group-begin dummy ignore-ip 1.2.3.4 ignore-ip 7.8.9.10 )"""; ofs.flush(); server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "7.8.9.10", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 20); server.MockPing(PING_TYPE_ICMP, "7.8.9.10", 60, 10); server.Start(R"""(bind [::]:60053 conf-file /tmp/smartdns_conf_file*.conf -g client server udp://127.0.0.1:61053 ignore-ip 1.2.3.4 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); auto ipaddr = smartdns::GetAvailableIPAddresses(); if (ipaddr.size() > 0) { ASSERT_TRUE(client.Query("a.com", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "7.8.9.10"); } } TEST_F(Group, speed_check) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; std::string file = "/tmp/smartdns_conf_file" + smartdns::GenerateRandomString(5) + ".conf"; std::ofstream ofs(file); ASSERT_TRUE(ofs.is_open()); Defer { ofs.close(); unlink(file.c_str()); }; ofs << R"""( server udp://127.0.0.1:61053 -e client-rules 127.0.0.1 speed-check-mode none )"""; ofs.flush(); server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "7.8.9.10", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 20); server.MockPing(PING_TYPE_ICMP, "7.8.9.10", 60, 10); server.Start(R"""(bind [::]:60053 conf-file /tmp/smartdns_conf_file*.conf -g client server udp://127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "7.8.9.10"); auto ipaddr = smartdns::GetAvailableIPAddresses(); if (ipaddr.size() > 0) { ASSERT_TRUE(client.Query("a.com", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "7.8.9.10"); } } TEST_F(Group, conf_inherit) { smartdns::MockServer server_upstream; smartdns::Server server; std::string file = "/tmp/smartdns_conf_file" + smartdns::GenerateRandomString(5) + ".conf"; std::ofstream ofs(file); ASSERT_TRUE(ofs.is_open()); Defer { ofs.close(); unlink(file.c_str()); }; ofs << R"""( server udp://127.0.0.1:61053 -e client-rules 127.0.0.1 )"""; ofs.flush(); server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "7.8.9.10", 611); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_TXT) { dns_add_TXT(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 6, "hello world"); return smartdns::SERVER_REQUEST_OK; } else { return smartdns::SERVER_REQUEST_SOA; } }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 50); server.MockPing(PING_TYPE_ICMP, "7.8.9.10", 60, 10); server.MockPing(PING_TYPE_ICMP, "64:ff9b::102:304", 60, 10); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 group-begin dummy speed-check-mode none force-AAAA-SOA yes force-qtype-SOA 16 conf-file /tmp/smartdns_conf_file*.conf -g client )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "7.8.9.10"); ASSERT_TRUE(client.Query("b.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("c.com TXT", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); auto ipaddr = smartdns::GetAvailableIPAddresses(); if (ipaddr.size() > 0) { ASSERT_TRUE(client.Query("a.com", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "7.8.9.10"); ASSERT_TRUE(client.Query("b.com AAAA", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); ASSERT_TRUE(client.Query("c.com TXT", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "c.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "\"hello world\""); } } TEST_F(Group, dualstack_inherit_ipv4_prefer) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 80); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 150); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 200); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping group-begin dummy group-begin client dualstack-ip-selection no client-rules 127.0.0.1 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); auto ipaddr = smartdns::GetAvailableIPAddresses(); if (ipaddr.size() > 0) { ASSERT_TRUE(client.Query("a.com AAAA", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } } TEST_F(Group, group_match_client_ip) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 80); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 150); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 200); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping group-begin client dualstack-ip-selection no group-match -client-ip 127.0.0.1 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); auto ipaddr = smartdns::GetAvailableIPAddresses(); if (ipaddr.size() > 0) { ASSERT_TRUE(client.Query("a.com AAAA", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com", 60053, ipaddr[0])); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } } TEST_F(Group, group_match_domain) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 80); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 150); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 200); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none group-begin client address #6 group-match -domain a.com )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("b.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "2001:db8::2"); ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8"); } TEST_F(Group, group_from_bind) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 80); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 150); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 200); server.Start(R"""(bind [::]:60053 bind [::]:60054 -g client server 127.0.0.1:61053 speed-check-mode none group-begin client address 1.1.1.1 group-match -domain b.com )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1"); ASSERT_TRUE(client.Query("a.com", 60054)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1"); } TEST_F(Group, server_group_exclude_default) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream1; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } usleep(50000); smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream1.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "2001::", 128, 10000); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 group-begin client server 127.0.0.1:61053 group-end server 127.0.0.1:62053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 600); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } TEST_F(Group, group_inherit) { smartdns::Server server; smartdns::MockServer server_upstream; server_upstream.Start("udp://0.0.0.0:61056", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } usleep(50000); smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 80); server.Start(R"""(bind [::]:60053 bind [::]:60054 -g group1 bind [::]:60055 -g group1_1 bind [::]:60056 -g group1_1_1 bind [::]:60057 -g group1_1_2 server 127.0.0.1:61053 address 1.1.1.0 speed-check-mode none group-begin group1 address 1.1.1.1 group-match -domain b.com address /b.com/1.1.1.3 group-begin group1_1 address /b.com/1.1.1.2 group-begin group1_1_1 -inherit none server 127.0.0.1:61056 group-end group-begin group1_1_2 -inherit default group-end group-end group-end )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.0"); ASSERT_TRUE(client.Query("b.com", 60054)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.3"); ASSERT_TRUE(client.Query("a.com", 60055)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1"); ASSERT_TRUE(client.Query("b.com", 60055)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.2"); ASSERT_TRUE(client.Query("a.com", 60056)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com", 60057)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.0"); } ================================================ FILE: test/cases/test-hosts.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class Hosts : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(Hosts, read) { smartdns::MockServer server_upstream; smartdns::Server server; std::string file = "/tmp/smartdns_test_hosts" + smartdns::GenerateRandomString(5) + ".hosts"; std::string file2 = "/tmp/smartdns_test_hosts" + smartdns::GenerateRandomString(5) + ".hosts"; std::string file3 = "/tmp/smartdns_test_hosts" + smartdns::GenerateRandomString(5) + ".hosts"; std::ofstream ofs(file); std::ofstream ofs2(file2); std::ofstream ofs3(file3); ASSERT_TRUE(ofs.is_open()); ASSERT_TRUE(ofs2.is_open()); ASSERT_TRUE(ofs3.is_open()); Defer { ofs.close(); unlink(file.c_str()); ofs2.close(); unlink(file2.c_str()); ofs3.close(); unlink(file3.c_str()); }; ofs << "1.2.3.4 b.com\n"; ofs << "64:ff9b::102:304 b.com\n"; ofs.flush(); ofs2 << "1.1.1.1 a.com 1.a.com 2.a.com\n"; ofs2 << "# 4.5.6.7 c.com\n"; ofs2.flush(); ofs3 << "127.0.0.1 d.com\n"; ofs3 << "::1 d.com\n"; ofs3 << "0.0.0.0 e.com\n"; ofs3 << ":: e.com\n"; ofs3.flush(); server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none hosts-file /tmp/*.hosts )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("b.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); ASSERT_TRUE(client.Query("-x 64:ff9b::102:304", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "b.com."); ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1"); ASSERT_TRUE(client.Query("-x 1.1.1.1", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "1.1.1.1.in-addr.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "a.com."); ASSERT_TRUE(client.Query("1.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "1.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1"); ASSERT_TRUE(client.Query("2.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "2.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1"); ASSERT_TRUE(client.Query("c.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "c.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 600); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("d.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "d.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "127.0.0.1"); ASSERT_TRUE(client.Query("d.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "d.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "::1"); ASSERT_TRUE(client.Query("e.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "e.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "0.0.0.0"); ASSERT_TRUE(client.Query("e.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "e.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "::"); } ================================================ FILE: test/cases/test-http.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "include/utils.h" #include "server.h" #include "smartdns/dns.h" #include "smartdns/http_parse.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class HTTP : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(HTTP, http1_1_request_parse) { const char *data = "GET /?q=question&lang=cn HTTP/1.1\r\n" "Host: www.example.com\r\n" "User-Agent: smartdns/46\r\n" "Accept: */*\r\n" "\r\n" "hello world"; struct http_head *http_head = http_head_init(1024, HTTP_VERSION_1_1); ASSERT_NE(http_head, nullptr); int ret = http_head_parse(http_head, (const unsigned char *)data, strlen(data)); ASSERT_GT(ret, 0); EXPECT_STREQ(http_head_get_httpversion(http_head), "HTTP/1.1"); EXPECT_EQ(http_head_get_method(http_head), HTTP_METHOD_GET); EXPECT_STREQ(http_head_get_url(http_head), "/"); EXPECT_STREQ(http_head_get_fields_value(http_head, "Host"), "www.example.com"); EXPECT_STREQ(http_head_get_fields_value(http_head, "User-Agent"), "smartdns/46"); EXPECT_STREQ(http_head_get_fields_value(http_head, "Accept"), "*/*"); EXPECT_STREQ((const char *)http_head_get_data(http_head), "hello world"); EXPECT_STREQ(http_head_get_params_value(http_head, "q"), "question"); EXPECT_STREQ(http_head_get_params_value(http_head, "lang"), "cn"); http_head_destroy(http_head); } TEST_F(HTTP, http1_1_request_serialize) { const char *data = "GET /?q=question&lang=cn HTTP/1.1\r\n" "Host: www.example.com\r\n" "User-Agent: smartdns/46\r\n" "Accept: */*\r\n" "\r\n" "hello world"; struct http_head *http_head = http_head_init(1024, HTTP_VERSION_1_1); ASSERT_NE(http_head, nullptr); http_head_set_httpversion(http_head, "HTTP/1.1"); http_head_set_method(http_head, HTTP_METHOD_GET); http_head_set_url(http_head, "/"); http_head_add_fields(http_head, "Host", "www.example.com"); http_head_add_fields(http_head, "User-Agent", "smartdns/46"); http_head_add_fields(http_head, "Accept", "*/*"); http_head_set_data(http_head, "hello world", strlen("hello world") + 1); http_head_set_head_type(http_head, HTTP_HEAD_REQUEST); http_head_add_param(http_head, "q", "question"); http_head_add_param(http_head, "lang", "cn"); char buffer[1024]; int ret = http_head_serialize(http_head, buffer, 1024); ASSERT_GT(ret, 0); EXPECT_STREQ(buffer, data); http_head_destroy(http_head); } TEST_F(HTTP, http1_1_response_parse) { const char *data = "HTTP/1.1 200 OK\r\n" "Server: smartdns\r\n" "Content-Type: text/html\r\n" "Content-Length: 11\r\n" "\r\n" "hello world"; struct http_head *http_head = http_head_init(1024, HTTP_VERSION_1_1); ASSERT_NE(http_head, nullptr); int ret = http_head_parse(http_head, (const unsigned char *)data, strlen(data)); ASSERT_GT(ret, 0); EXPECT_STREQ(http_head_get_httpversion(http_head), "HTTP/1.1"); EXPECT_EQ(http_head_get_httpcode(http_head), 200); EXPECT_STREQ(http_head_get_httpcode_msg(http_head), "OK"); EXPECT_STREQ(http_head_get_fields_value(http_head, "Server"), "smartdns"); EXPECT_STREQ(http_head_get_fields_value(http_head, "Content-Type"), "text/html"); EXPECT_STREQ(http_head_get_fields_value(http_head, "Content-Length"), "11"); EXPECT_STREQ((const char *)http_head_get_data(http_head), "hello world"); http_head_destroy(http_head); } TEST_F(HTTP, http1_1_response_serialize) { const char *data = "HTTP/1.1 200 OK\r\n" "Server: smartdns\r\n" "Content-Type: text/html\r\n" "Content-Length: 11\r\n" "\r\n" "hello world"; struct http_head *http_head = http_head_init(1024, HTTP_VERSION_1_1); ASSERT_NE(http_head, nullptr); http_head_set_httpversion(http_head, "HTTP/1.1"); http_head_set_httpcode(http_head, 200, "OK"); http_head_add_fields(http_head, "Server", "smartdns"); http_head_add_fields(http_head, "Content-Type", "text/html"); http_head_add_fields(http_head, "Content-Length", "11"); http_head_set_data(http_head, "hello world", strlen("hello world") + 1); http_head_set_head_type(http_head, HTTP_HEAD_RESPONSE); char buffer[1024]; int ret = http_head_serialize(http_head, buffer, 1024); ASSERT_GT(ret, 0); EXPECT_STREQ(buffer, data); http_head_destroy(http_head); } TEST_F(HTTP, http3_0_parse) { struct http_head *http_head = http_head_init(1024, HTTP_VERSION_3_0); ASSERT_NE(http_head, nullptr); http_head_set_httpversion(http_head, "HTTP/3"); http_head_set_method(http_head, HTTP_METHOD_GET); http_head_set_url(http_head, "/"); http_head_add_fields(http_head, "Host", "www.example.com"); http_head_add_fields(http_head, "User-Agent", "smartdns/46"); http_head_add_fields(http_head, "Accept", "*/*"); http_head_set_data(http_head, "hello world", strlen("hello world") + 1); http_head_set_head_type(http_head, HTTP_HEAD_REQUEST); http_head_add_param(http_head, "q", "question"); http_head_add_param(http_head, "lang", "cn"); unsigned char buffer[1024]; int ret = http_head_serialize(http_head, buffer, 1024); ASSERT_EQ(ret, 149); http_head_destroy(http_head); http_head = http_head_init(1024, HTTP_VERSION_3_0); ASSERT_NE(http_head, nullptr); ret = http_head_parse(http_head, buffer, ret); ASSERT_EQ(ret, 149); EXPECT_STREQ(http_head_get_httpversion(http_head), "HTTP/3.0"); EXPECT_EQ(http_head_get_method(http_head), HTTP_METHOD_GET); EXPECT_STREQ(http_head_get_url(http_head), "/"); EXPECT_STREQ(http_head_get_fields_value(http_head, "Host"), "www.example.com"); EXPECT_STREQ(http_head_get_fields_value(http_head, "User-Agent"), "smartdns/46"); EXPECT_STREQ(http_head_get_fields_value(http_head, "Accept"), "*/*"); EXPECT_STREQ((const char *)http_head_get_data(http_head), "hello world"); EXPECT_STREQ(http_head_get_params_value(http_head, "q"), "question"); EXPECT_STREQ(http_head_get_params_value(http_head, "lang"), "cn"); http_head_destroy(http_head); } TEST_F(HTTP, http1_1_small_buffer) { const char *data = "HTTP/1.1 200 OK\r\n" "Server: smartdns\r\n" "Content-Type: text/html\r\n" "Content-Length: 11\r\n" "\r\n" "hello world"; struct http_head *http_head = http_head_init(5, HTTP_VERSION_1_1); ASSERT_NE(http_head, nullptr); int ret = http_head_parse(http_head, (const unsigned char *)data, strlen(data)); EXPECT_EQ(ret, -3); http_head_destroy(http_head); } TEST_F(HTTP, http3_small_buffer) { struct http_head *http_head = http_head_init(1024, HTTP_VERSION_3_0); ASSERT_NE(http_head, nullptr); http_head_set_httpversion(http_head, "HTTP/3"); http_head_set_method(http_head, HTTP_METHOD_GET); http_head_set_url(http_head, "/"); http_head_add_fields(http_head, "Host", "www.example.com"); http_head_add_fields(http_head, "User-Agent", "smartdns/46"); http_head_add_fields(http_head, "Accept", "*/*"); http_head_set_data(http_head, "hello world", strlen("hello world") + 1); http_head_set_head_type(http_head, HTTP_HEAD_REQUEST); http_head_add_param(http_head, "q", "question"); http_head_add_param(http_head, "lang", "cn"); unsigned char buffer[1024]; int buffer_len = http_head_serialize(http_head, buffer, 1024); ASSERT_EQ(buffer_len, 149); http_head_destroy(http_head); http_head = http_head_init(5, HTTP_VERSION_3_0); ASSERT_NE(http_head, nullptr); int ret = http_head_parse(http_head, (const unsigned char *)buffer, buffer_len); EXPECT_EQ(ret, -3); http_head_destroy(http_head); http_head = http_head_init(100, HTTP_VERSION_3_0); ASSERT_NE(http_head, nullptr); ret = http_head_parse(http_head, (const unsigned char *)buffer, buffer_len); EXPECT_EQ(ret, -3); http_head_destroy(http_head); http_head = http_head_init(1024, HTTP_VERSION_3_0); ASSERT_NE(http_head, nullptr); ret = http_head_parse(http_head, (const unsigned char *)buffer, buffer_len); EXPECT_GT(ret, 0); http_head_destroy(http_head); } ================================================ FILE: test/cases/test-http2.cc ================================================ #include "client.h" #include "server.h" #include "smartdns/dns.h" #include "smartdns/http2.h" #include "gtest/gtest.h" #include #include #include #include // Test HTTP/2 with bind-https server (simulating upstream HTTPS server) TEST(HTTP2, BindServerHTTP2) { Defer { unlink("/tmp/smartdns-cert.pem"); unlink("/tmp/smartdns-key.pem"); }; smartdns::Server server_wrap; smartdns::Server server; // Start main SmartDNS instance that queries upstream HTTPS server server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2 log-level debug )"""); // Start upstream HTTPS server (bind-https) server_wrap.Start(R"""(bind-https [::]:60053 -alpn h2 address /example.com/1.2.3.4 address /test.com/5.6.7.8 log-level debug )"""); smartdns::Client client; // Test first query ASSERT_TRUE(client.Query("example.com", 61053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); // Test second query to verify connection reuse ASSERT_TRUE(client.Query("test.com", 61053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); } TEST(HTTP2, ServerMultiStream) { smartdns::Server server_wrap; smartdns::Server server; // Start main SmartDNS instance server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2 log-level debug )"""); // Start upstream HTTPS server (bind-https) server_wrap.Start(R"""(bind-https [::]:60053 -alpn h2 address /example.com/1.2.3.4 address /test.com/5.6.7.8 log-level debug )"""); smartdns::Client client; // Send multiple concurrent queries // Note: The smartdns::Client might be synchronous, so we might need threads or a way to send async. // But we can verify that multiple queries on the same connection work (multiplexing). // The previous test already verified connection reuse. // To verify concurrency, we'd need to delay the response on the server, which is hard with bind-https. // However, we can at least verify that sending many queries quickly works. for (int i = 0; i < 10; i++) { ASSERT_TRUE(client.Query("example.com", 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } } TEST(HTTP2, ServerALPNConfig) { smartdns::Server server_wrap; smartdns::Server server; // Case 1: Server supports h2, client requests h2 -> h2 server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2 log-level debug )"""); server_wrap.Start(R"""(bind-https [::]:60053 -alpn h2 address /example.com/1.2.3.4 log-level debug )"""); smartdns::Client client; ASSERT_TRUE(client.Query("example.com", 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); } TEST(HTTP2, ServerALPNFallback) { smartdns::Server server_wrap; smartdns::Server server; // Case 2: Server supports http/1.1 only, client requests h2 -> fallback or fail? // If client requests h2 only, it should fail. // If client requests h2,http/1.1, it should fallback. server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2,http/1.1 log-level debug )"""); server_wrap.Start(R"""(bind-https [::]:60053 -alpn http/1.1 address /example.com/1.2.3.4 log-level debug )"""); smartdns::Client client; ASSERT_TRUE(client.Query("example.com", 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); } // Test client only supports HTTP/1.1, server supports both TEST(HTTP2, ClientHTTP1Only) { smartdns::Server server_wrap; smartdns::Server server; // Client only supports http/1.1, server supports both h2 and http/1.1 server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn http/1.1 log-level debug )"""); server_wrap.Start(R"""(bind-https [::]:60053 -alpn h2,http/1.1 address /example.com/1.2.3.4 log-level debug )"""); smartdns::Client client; ASSERT_TRUE(client.Query("example.com", 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } // Test both client and server only support HTTP/1.1 TEST(HTTP2, BothHTTP1Only) { smartdns::Server server_wrap; smartdns::Server server; // Both client and server only support http/1.1 server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn http/1.1 log-level debug )"""); server_wrap.Start(R"""(bind-https [::]:60053 -alpn http/1.1 address /example.com/1.2.3.4 address /test2.com/9.10.11.12 log-level debug )"""); smartdns::Client client; // First query ASSERT_TRUE(client.Query("example.com", 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); // Second query to verify connection reuse with HTTP/1.1 ASSERT_TRUE(client.Query("test2.com", 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "9.10.11.12"); } // Test concurrent queries from multiple clients TEST(HTTP2, ConcurrentClients) { smartdns::Server server_wrap; smartdns::Server server; server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2 log-level debug )"""); server_wrap.Start(R"""(bind-https [::]:60053 -alpn h2 address /example.com/1.2.3.4 address /test.com/5.6.7.8 log-level debug )"""); // Create multiple threads to query simultaneously std::vector threads; std::atomic success_count{0}; for (int i = 0; i < 5; i++) { threads.emplace_back([&success_count, i]() { smartdns::Client client; const char* domain = (i % 2 == 0) ? "example.com" : "test.com"; const char* expected_ip = (i % 2 == 0) ? "1.2.3.4" : "5.6.7.8"; if (client.Query(domain, 61053)) { if (client.GetStatus() == "NOERROR" && client.GetAnswerNum() > 0 && client.GetAnswer()[0].GetData() == expected_ip) { success_count++; } } }); } for (auto& t : threads) { t.join(); } EXPECT_EQ(success_count.load(), 5); } // Test mixed HTTP/2 and HTTP/1.1 queries TEST(HTTP2, MixedProtocolQueries) { smartdns::Server server_wrap_h2; smartdns::Server server_wrap_http1; smartdns::Server server; // Main server supports both protocols server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2,http/1.1 server https://127.0.0.1:60054/dns-query -no-check-certificate -alpn http/1.1 log-level debug )"""); // First upstream supports HTTP/2 server_wrap_h2.Start(R"""(bind-https [::]:60053 -alpn h2 address /h2-domain.com/1.1.1.1 log-level debug )"""); // Second upstream supports HTTP/1.1 only server_wrap_http1.Start(R"""(bind-https [::]:60054 -alpn http/1.1 address /http1-domain.com/2.2.2.2 log-level debug )"""); smartdns::Client client; // Query from HTTP/2 server ASSERT_TRUE(client.Query("h2-domain.com", 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1"); // Query from HTTP/1.1 server ASSERT_TRUE(client.Query("http1-domain.com", 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2.2.2.2"); } // Test connection reuse for HTTP/2 TEST(HTTP2, ConnectionReuse) { smartdns::Server server_wrap; smartdns::Server server; server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2 log-level debug )"""); server_wrap.Start(R"""(bind-https [::]:60053 -alpn h2 address /domain1.com/1.1.1.1 address /domain2.com/2.2.2.2 address /domain3.com/3.3.3.3 log-level debug )"""); smartdns::Client client; // Multiple queries that should reuse the same HTTP/2 connection for (int i = 1; i <= 3; i++) { std::string domain = "domain" + std::to_string(i) + ".com"; std::string expected_ip = std::to_string(i) + "." + std::to_string(i) + "." + std::to_string(i) + "." + std::to_string(i); ASSERT_TRUE(client.Query(domain.c_str(), 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), expected_ip); } } // Test default ALPN behavior (no explicit -alpn parameter) TEST(HTTP2, DefaultALPN) { smartdns::Server server_wrap; smartdns::Server server; // Client doesn't specify ALPN (should default to supporting both) server.Start(R"""(bind [::]:61053 server https://127.0.0.1:60053/dns-query -no-check-certificate log-level debug )"""); // Server supports both (no explicit -alpn, should default to h2,http/1.1) server_wrap.Start(R"""(bind-https [::]:60053 address /example.com/1.2.3.4 log-level debug )"""); smartdns::Client client; ASSERT_TRUE(client.Query("example.com", 61053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-https.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class HTTPS : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(HTTPS, ipv4_speed_prefer) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "b.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 b.com. alpn=\"h2,h3-19\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA="); } TEST_F(HTTPS, ipv6_speed_prefer) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "b.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "102:304:506:708:90a:b0c:d0e:f10", 60, 10); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 b.com. alpn=\"h2,h3-19\" port=443 ech=AEX+DQA= ipv6hint=102:304:506:708:90a:b0c:d0e:f10"); } TEST_F(HTTPS, ipv4_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no address /a.com/#4 log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 61053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); auto result_check = client.GetAnswer()[0].GetData(); ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 a.com. alpn=\"h2,h3-19\" port=443 ech=AEX+DQA= ipv6hint=102:304:506:708:90a:b0c:d0e:f10"); } TEST_F(HTTPS, ipv6_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no address /a.com/#6 log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 61053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); auto result_check = client.GetAnswer()[0].GetData(); ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 a.com. alpn=\"h2,h3-19\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA="); } TEST_F(HTTPS, UPSTREAM_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no address /a.com/#6 log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_GT(client.GetAuthority()[0].GetTTL(), 595); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } TEST_F(HTTPS, HTTPS_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; } if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no https-record /a.com/# log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(HTTPS, HTTPS_IGN) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no force-qtype-SOA 65 https-record /a.com/- log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com HTTPS", 61053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); auto result_check = client.GetAnswer()[0].GetData(); ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 a.com. alpn=\"h2,h3-19\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA="); } TEST_F(HTTPS, HTTPS_IGN_WITH_RULE) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no force-qtype-SOA 65 https-record /a.com/noipv4hint,noipv6hint,noech log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com HTTPS", 61053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); auto result_check = client.GetAnswer()[0].GetData(); ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 a.com. alpn=\"h2,h3-19\" port=443"); } TEST_F(HTTPS, HTTPS_DOMAIN_RULE_IGN) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no address # domain-rules /a.com/ -https-record - log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com HTTPS", 61053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); auto result_check = client.GetAnswer()[0].GetData(); ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 a.com. alpn=\"h2,h3-19\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA="); } TEST_F(HTTPS, multi_https_record) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no force-qtype-SOA 65 https-record /a.com/target=b.com,priority=1,port=1443,alpn=\"h2,h3-19\",ech=\"AEX+DQA=\",ipv4hint=1.2.3.4 https-record /a.com/target=b.com,priority=2,port=2443,alpn=\"h2,h3-19\",ech=\"AEX+DQA=\",ipv4hint=1.2.3.4 log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 b.com. alpn=\"h2,h3-19\" port=1443 ipv4hint=1.2.3.4 ech=AEX+DQA="); EXPECT_EQ(client.GetAnswer()[1].GetData(), "2 b.com. alpn=\"h2,h3-19\" port=2443 ipv4hint=1.2.3.4 ech=AEX+DQA="); } TEST_F(HTTPS, https_record) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no force-qtype-SOA 65 https-record /a.com/target=b.com,port=1443,alpn=\"h2,h3-19\",ech=\"AEX+DQA=\",ipv4hint=1.2.3.4 log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 b.com. alpn=\"h2,h3-19\" port=1443 ipv4hint=1.2.3.4 ech=AEX+DQA="); } TEST_F(HTTPS, filter_ip) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "b.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no https-record noipv4hint,noipv6hint log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 b.com. alpn=\"h2,h3-19\" port=443 ech=AEX+DQA="); } TEST_F(HTTPS, multi_filter_ip) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; { dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 300, 1, "b.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); } { dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 300, 2, "c.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {5, 6, 7, 8}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); } return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 10); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no https-record noipv4hint,noipv6hint,noech log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ( client.GetAnswer()[0].GetData(), "1 b.com. alpn=\"h2,h3-19\" port=443"); EXPECT_EQ(client.GetAnswer()[1].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[1].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[1].GetType(), "HTTPS"); EXPECT_EQ( client.GetAnswer()[1].GetData(), "2 c.com. alpn=\"h2,h3-19\" port=443"); } TEST_F(HTTPS, BIND_FORCE_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; } if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.Start(R"""(bind [::]:60053 bind [::]:62053 -force-https-soa server 127.0.0.1:61053 log-console yes dualstack-ip-selection no log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 62053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetType(), "HTTPS"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 a.com. alpn=\"h2,h3-19\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA="); } ================================================ FILE: test/cases/test-idna.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class IDNA : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(IDNA, match) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /中国.com/10.10.10.10 address /中国.com/64:ff9b::1010:1010 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("xn--fiqs8s.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "xn--fiqs8s.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "10.10.10.10"); ASSERT_TRUE(client.Query("xn--fiqs8s.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "xn--fiqs8s.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::1010:1010"); } ================================================ FILE: test/cases/test-ip-alias.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" #include class IPAlias : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST(IPAlias, map_multiip_nospeed_check) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[][4] = {{1, 2, 3, 1}, {1, 2, 3, 2}, {1, 2, 3, 3}}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]); dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, {1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]); dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "9.10.11.12", 60, 140); server.MockPing(PING_TYPE_ICMP, "10.10.10.10", 60, 120); server.MockPing(PING_TYPE_ICMP, "11.11.11.11", 60, 150); server.MockPing(PING_TYPE_ICMP, "0102:0304:0500::", 60, 100); server.MockPing(PING_TYPE_ICMP, "0506:0708:0900::", 60, 110); server.MockPing(PING_TYPE_ICMP, "0a0b:0c0d:0e00::", 60, 140); server.MockPing(PING_TYPE_ICMP, "ffff::1", 60, 120); server.MockPing(PING_TYPE_ICMP, "ffff::2", 60, 150); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no speed-check-mode none ip-alias 1.2.3.0/24 10.10.10.10,12.12.12.12,13.13.13.13,15.15.15.15 ip-alias 0102::/16 FFFF::0001,FFFF::0002,FFFF::0003,FFFF::0004 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 4); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "10.10.10.10"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "12.12.12.12"); EXPECT_EQ(client.GetAnswer()[2].GetData(), "15.15.15.15"); EXPECT_EQ(client.GetAnswer()[3].GetData(), "13.13.13.13"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 4); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "ffff::1"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "ffff::3"); EXPECT_EQ(client.GetAnswer()[2].GetData(), "ffff::2"); EXPECT_EQ(client.GetAnswer()[3].GetData(), "ffff::4"); } TEST(IPAlias, map_single_ip_nospeed_check) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]); dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]); dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "9.10.11.12", 60, 140); server.MockPing(PING_TYPE_ICMP, "10.10.10.10", 60, 120); server.MockPing(PING_TYPE_ICMP, "11.11.11.11", 60, 150); server.MockPing(PING_TYPE_ICMP, "0102:0304:0500::", 60, 100); server.MockPing(PING_TYPE_ICMP, "0506:0708:0900::", 60, 110); server.MockPing(PING_TYPE_ICMP, "0a0b:0c0d:0e00::", 60, 140); server.MockPing(PING_TYPE_ICMP, "ffff::1", 60, 120); server.MockPing(PING_TYPE_ICMP, "ffff::2", 60, 150); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no speed-check-mode none ip-alias 1.2.3.4 10.10.10.10 ip-alias 5.6.7.8/32 11.11.11.11 ip-alias 0102:0304:0500:: ffff::1 ip-alias 0506:0708:0900:: ffff::2 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 3); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "10.10.10.10"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "11.11.11.11"); EXPECT_EQ(client.GetAnswer()[2].GetData(), "9.10.11.12"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 3); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "ffff::1"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "a0b:c0d:e00::"); EXPECT_EQ(client.GetAnswer()[2].GetData(), "ffff::2"); } TEST(IPAlias, mapip_withspeed_check) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]); dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]); dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "9.10.11.12", 60, 140); server.MockPing(PING_TYPE_ICMP, "10.10.10.10", 60, 120); server.MockPing(PING_TYPE_ICMP, "11.11.11.11", 60, 150); server.MockPing(PING_TYPE_ICMP, "0102:0304:0500::", 60, 100); server.MockPing(PING_TYPE_ICMP, "0506:0708:0900::", 60, 110); server.MockPing(PING_TYPE_ICMP, "0a0b:0c0d:0e00::", 60, 140); server.MockPing(PING_TYPE_ICMP, "ffff::1", 60, 120); server.MockPing(PING_TYPE_ICMP, "ffff::2", 60, 150); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no ip-alias 1.2.3.4 10.10.10.10 ip-alias 5.6.7.8/32 11.11.11.11 ip-alias 0102::/16 ffff::1 ip-alias 0506::/16 ffff::2 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "10.10.10.10"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "ffff::1"); } TEST(IPAlias, no_ip_alias) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]); dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]); dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.MockPing(PING_TYPE_ICMP, "9.10.11.12", 60, 140); server.MockPing(PING_TYPE_ICMP, "10.10.10.10", 60, 120); server.MockPing(PING_TYPE_ICMP, "11.11.11.11", 60, 150); server.MockPing(PING_TYPE_ICMP, "0102:0304:0500::", 60, 100); server.MockPing(PING_TYPE_ICMP, "0506:0708:0900::", 60, 110); server.MockPing(PING_TYPE_ICMP, "0a0b:0c0d:0e00::", 60, 140); server.MockPing(PING_TYPE_ICMP, "ffff::1", 60, 120); server.MockPing(PING_TYPE_ICMP, "ffff::2", 60, 150); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no ip-alias 1.2.3.4 10.10.10.10 ip-alias 5.6.7.8/32 11.11.11.11 ip-alias 0102::/16 ffff::1 ip-alias 0506::/16 ffff::2 domain-rules /a.com/ -no-ip-alias )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "102:304:500::"); } ================================================ FILE: test/cases/test-ip-rule.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" class IPRule : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(IPRule, white_list) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -whitelist-ip server udp://127.0.0.1:62053 -whitelist-ip whitelist-ip 4.5.6.7/24 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "4.5.6.7"); } TEST_F(IPRule, white_list_not_in) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "9.10.11.12", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -whitelist-ip server udp://127.0.0.1:62053 -whitelist-ip whitelist-ip 4.5.6.7/24 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); } TEST_F(IPRule, black_list) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } usleep(800000); smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "4.5.6.7", 60, 10); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -blacklist-ip server udp://127.0.0.1:62053 -blacklist-ip blacklist-ip 4.5.6.7/24 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(IPRule, ignore_ip) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "7.8.9.10", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.MockPing(PING_TYPE_ICMP, "4.5.6.7", 60, 90); server.MockPing(PING_TYPE_ICMP, "7.8.9.10", 60, 40); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -blacklist-ip ignore-ip 1.2.3.0/24 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "7.8.9.10"); } TEST_F(IPRule, ignore_ip_set) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; std::string file = "/tmp/smartdns_test_ip_set.list" + smartdns::GenerateRandomString(5); std::ofstream ofs(file); ASSERT_TRUE(ofs.is_open()); Defer { ofs.close(); unlink(file.c_str()); }; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "7.8.9.10", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.MockPing(PING_TYPE_ICMP, "4.5.6.7", 60, 90); server.MockPing(PING_TYPE_ICMP, "7.8.9.10", 60, 40); std::string ipset_list = R"""( 1.2.3.0/24 4.5.6.0/24 )"""; ofs.write(ipset_list.c_str(), ipset_list.length()); ofs.flush(); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -blacklist-ip ip-set -name ip-list -file )""" + file + R"""( ignore-ip ip-set:ip-list speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "7.8.9.10"); } TEST_F(IPRule, ignore_all_ip) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "7.8.9.10", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.MockPing(PING_TYPE_ICMP, "4.5.6.7", 60, 90); server.MockPing(PING_TYPE_ICMP, "7.8.9.10", 60, 40); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -blacklist-ip ignore-ip 0.0.0.0/0 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); } TEST_F(IPRule, no_ignore_ip_by_domain) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "7.8.9.10", 611); return smartdns::SERVER_REQUEST_OK; }); /* this ip will be discard, but is reachable */ server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.MockPing(PING_TYPE_ICMP, "4.5.6.7", 60, 90); server.MockPing(PING_TYPE_ICMP, "7.8.9.10", 60, 40); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -blacklist-ip ignore-ip 0.0.0.0/0 domain-rules /b.com/ -no-ignore-ip )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(IPRule, ip_alias_ip_set) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; std::string file = "/tmp/smartdns_test_ip_set.list" + smartdns::GenerateRandomString(5); std::string file_ip = "/tmp/smartdns_test_ip_set_ip.list" + smartdns::GenerateRandomString(5); std::ofstream ofs(file); std::ofstream ofs_ip(file_ip); ASSERT_TRUE(ofs.is_open()); ASSERT_TRUE(ofs_ip.is_open()); Defer { ofs.close(); unlink(file.c_str()); ofs_ip.close(); unlink(file_ip.c_str()); }; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "4.5.6.7", 611); smartdns::MockServer::AddIP(request, request->domain.c_str(), "7.8.9.10", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10); server.MockPing(PING_TYPE_ICMP, "4.5.6.7", 60, 90); server.MockPing(PING_TYPE_ICMP, "7.8.9.10", 60, 40); std::string ipset_list = R"""( 1.2.3.0/24 4.5.6.0/24 7.8.9.0/24 )"""; ofs.write(ipset_list.c_str(), ipset_list.length()); ofs.flush(); std::string ipset_list_ip = R"""( 1.1.1.1 )"""; ofs_ip.write(ipset_list_ip.c_str(), ipset_list_ip.length()); ofs_ip.flush(); server.Start(R"""(bind [::]:60053 server udp://127.0.0.1:61053 -blacklist-ip ip-set -name ip-list -file )""" + file + R"""( ip-set -name ip-list-ip -file )""" + file_ip + R"""( ip-alias ip-set:ip-list ip-set:ip-list-ip speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.1.1.1"); } ================================================ FILE: test/cases/test-lib-http2.cc ================================================ #include "gtest/gtest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smartdns/http2.h" class LIBHTTP2 : public ::testing::Test { protected: void SetUp() override { // Create socketpair for communication if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) < 0) { perror("socketpair"); FAIL() << "Failed to create socketpair"; } client_sock = socks[0]; server_sock = socks[1]; // Set non-blocking fcntl(client_sock, F_SETFL, O_NONBLOCK); fcntl(server_sock, F_SETFL, O_NONBLOCK); } void TearDown() override { if (client_sock != -1) close(client_sock); if (server_sock != -1) close(server_sock); } int socks[2]; int client_sock = -1; int server_sock = -1; // BIO callbacks static int bio_read(void *private_data, uint8_t *buf, int len) { int fd = *(int *)private_data; int ret = read(fd, buf, len); if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { errno = EAGAIN; return -1; } return ret; } static int bio_write(void *private_data, const uint8_t *buf, int len) { int fd = *(int *)private_data; int ret = write(fd, buf, len); if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { errno = EAGAIN; return -1; } return ret; } }; TEST_F(LIBHTTP2, Integrated) { std::thread server_thread([this]() { // Server logic struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake int handshake_attempts = 200; int ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {server_sock, POLLIN, 0}; int poll_ret = poll(&pfd, 1, 10); if (poll_ret == 0) { continue; } ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Server handshake failed"; // Accept stream struct http2_stream *stream = nullptr; int max_attempts = 200; while (max_attempts-- > 0 && !stream) { struct pollfd pfd = {server_sock, POLLIN, 0}; poll(&pfd, 1, 100); struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); for (int i = 0; i < count; i++) { if (items[i].stream == nullptr && items[i].readable) { stream = http2_ctx_accept_stream(ctx); if (stream) break; } } usleep(20000); } if (!stream) { std::cout << "Server failed to accept stream after timeout" << std::endl; } ASSERT_NE(stream, nullptr) << "Server failed to accept stream"; // Read request body uint8_t request_body[4096]; int request_body_len = 0; while (!http2_stream_is_end(stream) && request_body_len < (int)sizeof(request_body)) { int read_len = http2_stream_read_body(stream, request_body + request_body_len, sizeof(request_body) - request_body_len); if (read_len > 0) { request_body_len += read_len; } else { usleep(10000); } } // Send response char response[8192]; int response_len = snprintf(response, sizeof(response), "Echo Response: %.*s", request_body_len, request_body); char content_length[32]; snprintf(content_length, sizeof(content_length), "%d", response_len); struct http2_header_pair headers[] = {{"content-type", "text/plain"}, {"content-length", content_length}}; http2_stream_set_response(stream, 200, headers, 2); http2_stream_write_body(stream, (const uint8_t *)response, response_len, 1); http2_stream_close(stream); http2_ctx_close(ctx); }); std::thread client_thread([this]() { usleep(500000); // Wait for server start struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake int handshake_attempts = 200; int ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 10); ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Client handshake failed"; // Create stream struct http2_stream *stream = http2_stream_new(ctx); ASSERT_NE(stream, nullptr); // Send request struct http2_header_pair headers[] = { {"content-type", "application/json"}, {"content-length", "27"}, {NULL, NULL}}; http2_stream_set_request(stream, "POST", "/echo", NULL, headers); const char *request_body = "{\"message\":\"Hello Echo!\"}"; http2_stream_write_body(stream, (const uint8_t *)request_body, strlen(request_body), 1); // Wait for response int max_attempts = 200; while (max_attempts-- > 0) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 100); struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); if (http2_stream_get_status(stream) > 0) break; usleep(20000); } EXPECT_EQ(http2_stream_get_status(stream), 200); // Read response uint8_t response_body[4096]; int response_body_len = 0; while (!http2_stream_is_end(stream) && response_body_len < (int)sizeof(response_body)) { int read_len = http2_stream_read_body(stream, response_body + response_body_len, sizeof(response_body) - response_body_len); if (read_len > 0) { response_body_len += read_len; } else { usleep(10000); } } std::string resp((char *)response_body, response_body_len); EXPECT_NE(resp.find("Echo Response"), std::string::npos); http2_stream_close(stream); http2_ctx_close(ctx); }); server_thread.join(); client_thread.join(); } TEST_F(LIBHTTP2, MultiStream) { const int NUM_STREAMS = 3; std::thread server_thread([this, NUM_STREAMS]() { struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake int handshake_attempts = 200; int ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {server_sock, POLLIN, 0}; poll(&pfd, 1, 10); ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Server handshake failed"; int streams_completed = 0; int max_iterations = 500; std::set processed_streams; while (streams_completed < NUM_STREAMS && max_iterations-- > 0) { struct pollfd pfd = {server_sock, POLLIN, 0}; poll(&pfd, 1, 100); struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); for (int i = 0; i < count; i++) { if (items[i].stream == nullptr && items[i].readable) { struct http2_stream *s = http2_ctx_accept_stream(ctx); } else if (items[i].stream && items[i].readable) { struct http2_stream *stream = items[i].stream; uint8_t buf[1024]; http2_stream_read_body(stream, buf, sizeof(buf)); if (http2_stream_is_end(stream)) { if (processed_streams.find(stream) == processed_streams.end()) { char response[256]; int response_len = snprintf(response, sizeof(response), "Echo from stream %d", http2_stream_get_id(stream)); char content_length[32]; snprintf(content_length, sizeof(content_length), "%d", response_len); struct http2_header_pair headers[] = {{"content-type", "text/plain"}, {"content-length", content_length}}; http2_stream_set_response(stream, 200, headers, 2); http2_stream_write_body(stream, (const uint8_t *)response, response_len, 1); streams_completed++; processed_streams.insert(stream); } } } } usleep(2000); } for (auto stream : processed_streams) { http2_stream_close(stream); } http2_ctx_close(ctx); }); std::thread client_thread([this, NUM_STREAMS]() { usleep(50000); struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake int handshake_attempts = 200; int ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 10); ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Client handshake failed"; struct http2_stream *streams[NUM_STREAMS]; for (int i = 0; i < NUM_STREAMS; i++) { streams[i] = http2_stream_new(ctx); ASSERT_NE(streams[i], nullptr); char path[64]; snprintf(path, sizeof(path), "/stream%d", i); char body[128]; int body_len = snprintf(body, sizeof(body), "Request from stream %d", i); char content_length[32]; snprintf(content_length, sizeof(content_length), "%d", body_len); struct http2_header_pair headers[] = { {"content-type", "text/plain"}, {"content-length", content_length}, {NULL, NULL}}; http2_stream_set_request(streams[i], "POST", path, NULL, headers); http2_stream_write_body(streams[i], (const uint8_t *)body, body_len, 1); } int streams_completed = 0; int max_iterations = 500; std::set completed_stream_ids; while (streams_completed < NUM_STREAMS && max_iterations-- > 0) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 100); struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); for (int i = 0; i < count; i++) { if (items[i].stream && items[i].readable) { struct http2_stream *stream = items[i].stream; uint8_t buf[1024]; http2_stream_read_body(stream, buf, sizeof(buf)); if (http2_stream_is_end(stream)) { int stream_id = http2_stream_get_id(stream); if (completed_stream_ids.find(stream_id) == completed_stream_ids.end()) { completed_stream_ids.insert(stream_id); streams_completed++; } } } } usleep(2000); } EXPECT_EQ(streams_completed, NUM_STREAMS); for (int i = 0; i < NUM_STREAMS; i++) { http2_stream_close(streams[i]); } http2_ctx_close(ctx); }); server_thread.join(); client_thread.join(); } TEST_F(LIBHTTP2, EarlyStreamCreation) { std::thread server_thread([this]() { // Server logic struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake int handshake_attempts = 200; int ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {server_sock, POLLIN, 0}; int poll_ret = poll(&pfd, 1, 10); if (poll_ret == 0) { continue; } ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Server handshake failed"; // Accept stream struct http2_stream *stream = nullptr; int max_attempts = 200; while (max_attempts-- > 0 && !stream) { struct pollfd pfd = {server_sock, POLLIN, 0}; poll(&pfd, 1, 100); struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); for (int i = 0; i < count; i++) { if (items[i].stream == nullptr && items[i].readable) { stream = http2_ctx_accept_stream(ctx); if (stream) break; } } usleep(20000); } ASSERT_NE(stream, nullptr) << "Server failed to accept stream"; // Verify we received the request const char *method = http2_stream_get_method(stream); const char *path = http2_stream_get_path(stream); EXPECT_STREQ(method, "POST"); EXPECT_STREQ(path, "/early-test"); // Read request body (should be empty for GET) uint8_t request_body[4096]; int request_body_len = 0; while (!http2_stream_is_end(stream) && request_body_len < (int)sizeof(request_body)) { int read_len = http2_stream_read_body(stream, request_body + request_body_len, sizeof(request_body) - request_body_len); if (read_len > 0) { request_body_len += read_len; } else { usleep(10000); } } // Send response char response[8192]; int response_len = snprintf(response, sizeof(response), "Echo Response: %.*s", request_body_len, request_body); char content_length[32]; snprintf(content_length, sizeof(content_length), "%d", response_len); struct http2_header_pair headers[] = { {"content-type", "text/plain"}, {"content-length", content_length}, {NULL, NULL}}; http2_stream_set_response(stream, 200, headers, 2); http2_stream_write_body(stream, (const uint8_t *)response, response_len, 1); http2_stream_close(stream); http2_ctx_close(ctx); }); std::thread client_thread([this]() { usleep(50000); // Wait for server start // Create client context struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL); ASSERT_NE(ctx, nullptr); // IMPORTANT: Create stream and send request BEFORE handshake completes // This tests that the HEADERS frame is buffered and sent after handshake struct http2_stream *stream = http2_stream_new(ctx); ASSERT_NE(stream, nullptr); // Send request immediately (before handshake) struct http2_header_pair headers[] = {{"user-agent", "test-client"}, {NULL, NULL}}; int ret = http2_stream_set_request(stream, "POST", "/early-test", NULL, headers); EXPECT_EQ(ret, 0) << "Failed to set request"; const char *request_body = "test echo"; http2_stream_write_body(stream, (const uint8_t *)request_body, strlen(request_body), 1); // Now complete handshake int handshake_attempts = 200; ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 10); ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Client handshake failed"; // Wait for response int max_attempts = 200; while (max_attempts-- > 0) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 100); struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); if (http2_stream_get_status(stream) > 0) break; usleep(20000); } EXPECT_EQ(http2_stream_get_status(stream), 200); // Read response uint8_t response_body[4096]; int response_body_len = 0; while (!http2_stream_is_end(stream) && response_body_len < (int)sizeof(response_body)) { int read_len = http2_stream_read_body(stream, response_body + response_body_len, sizeof(response_body) - response_body_len); if (read_len > 0) { response_body_len += read_len; } else { usleep(10000); } } std::string resp((char *)response_body, response_body_len); EXPECT_NE(resp.find("Echo Response"), std::string::npos); EXPECT_NE(resp.find("test echo"), std::string::npos); http2_stream_close(stream); http2_ctx_close(ctx); }); server_thread.join(); client_thread.join(); } TEST_F(LIBHTTP2, ServerLoopTerminationOnDisconnect) { std::thread server_thread([this]() { struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake int handshake_attempts = 200; int ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {server_sock, POLLIN, 0}; int poll_ret = poll(&pfd, 1, 10); if (poll_ret == 0) { continue; } ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Server handshake failed"; // Accept stream struct http2_stream *stream = nullptr; int max_attempts = 200; while (max_attempts-- > 0 && !stream) { struct pollfd pfd = {server_sock, POLLIN, 0}; poll(&pfd, 1, 100); struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); for (int i = 0; i < count; i++) { if (items[i].stream == nullptr && items[i].readable) { stream = http2_ctx_accept_stream(ctx); if (stream) break; } } usleep(20000); } ASSERT_NE(stream, nullptr) << "Server failed to accept stream"; // Read request body until EOF uint8_t buf[1024]; int loop_count = 0; while (loop_count++ < 100) { struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); int data_read = 0; for (int i = 0; i < count; i++) { if (items[i].stream == stream && items[i].readable) { int ret = http2_stream_read_body(stream, buf, sizeof(buf)); if (ret > 0) { data_read = 1; } else if (ret == 0) { // EOF received data_read = 1; } } if (items[i].stream) { http2_stream_put(items[i].stream); } } if (!data_read && http2_stream_is_end(stream)) { // If we are here, it means poll returned 0 items (or stream not readable), // which is correct behavior after EOF is consumed. // If the bug exists, poll would keep returning readable stream, and we would keep reading 0 bytes. break; } usleep(10000); } EXPECT_LT(loop_count, 100) << "Server loop did not terminate (infinite loop detected)"; http2_ctx_close(ctx); }); std::thread client_thread([this]() { usleep(50000); struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL); ASSERT_NE(ctx, nullptr); int handshake_attempts = 200; int ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 10); ret = http2_ctx_handshake(ctx); if (ret != 0) { break; } } ASSERT_EQ(ret, 1); struct http2_stream *stream = http2_stream_new(ctx); ASSERT_NE(stream, nullptr); struct http2_header_pair headers[] = {{"content-type", "text/plain"}, {NULL, NULL}}; http2_stream_set_request(stream, "POST", "/test", NULL, headers); http2_stream_write_body(stream, (const uint8_t *)"test", 4, 1); http2_stream_close(stream); http2_ctx_close(ctx); }); server_thread.join(); client_thread.join(); } TEST_F(LIBHTTP2, StreamClose) { std::thread server_thread([this]() { struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake int handshake_attempts = 200; int ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {server_sock, POLLIN, 0}; int poll_ret = poll(&pfd, 1, 10); if (poll_ret == 0) { continue; } ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Server handshake failed"; // Accept stream struct http2_stream *stream = nullptr; int max_attempts = 200; while (max_attempts-- > 0 && !stream) { struct pollfd pfd = {server_sock, POLLIN, 0}; poll(&pfd, 1, 100); struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); for (int i = 0; i < count; i++) { if (items[i].stream == nullptr && items[i].readable) { stream = http2_ctx_accept_stream(ctx); if (stream) break; } } usleep(20000); } ASSERT_NE(stream, nullptr) << "Server failed to accept stream"; // Read request and send response uint8_t buf[1024]; http2_stream_read_body(stream, buf, sizeof(buf)); http2_stream_set_response(stream, 200, NULL, 0); http2_stream_write_body(stream, (const uint8_t *)"OK", 2, 1); http2_stream_close(stream); http2_ctx_close(ctx); }); std::thread client_thread([this]() { usleep(50000); struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake int handshake_attempts = 200; int ret = 0; while (handshake_attempts-- > 0) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 10); ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Client handshake failed"; // Create stream struct http2_stream *stream = http2_stream_new(ctx); ASSERT_NE(stream, nullptr); // Send request http2_stream_set_request(stream, "GET", "/test", NULL, NULL); http2_stream_write_body(stream, NULL, 0, 1); // Wait for response int max_attempts = 200; while (max_attempts-- > 0) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 100); struct http2_poll_item items[10]; int count = 0; http2_ctx_poll(ctx, items, 10, &count); if (http2_stream_get_status(stream) > 0) break; usleep(20000); } // Close the stream explicitly http2_stream_get(stream); // Keep reference for reading after close http2_stream_close(stream); // Verify stream is marked as closed (should still be able to read) // After close, the stream should still be readable until all data is consumed EXPECT_FALSE(http2_stream_is_end(stream)); // Should not be end yet since we haven't read response // Read response (should still work after close) uint8_t buf[1024]; int read_len = http2_stream_read_body(stream, buf, sizeof(buf)); EXPECT_GE(read_len, 0); // Should be able to read // After reading all data, stream should be end while (!http2_stream_is_end(stream)) { read_len = http2_stream_read_body(stream, buf, sizeof(buf)); if (read_len <= 0) { break; } } EXPECT_TRUE(http2_stream_is_end(stream)); // Should be end after reading all data http2_stream_put(stream); http2_ctx_put(ctx); }); server_thread.join(); client_thread.join(); } TEST_F(LIBHTTP2, ReferenceCountingNormal) { // Test normal reference counting: ctx normal, stream released by business struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL); ASSERT_NE(ctx, nullptr); // Create a stream (already has refcount = 1) struct http2_stream *stream = http2_stream_new(ctx); ASSERT_NE(stream, nullptr); // Close context (should not free stream because business still holds reference) http2_ctx_close(ctx); // Business releases reference http2_stream_close(stream); // Now stream should be freed // We can't directly check, but no crash should occur } TEST_F(LIBHTTP2, ReferenceCountingContextError) { // Test reference counting when ctx has error but stream is still referenced by business struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL); ASSERT_NE(ctx, nullptr); // Create a stream struct http2_stream *stream = http2_stream_new(ctx); ASSERT_NE(stream, nullptr); // Simulate context error by closing the socket (connection broken) close(client_sock); client_sock = -1; // Close context (should handle error gracefully) http2_ctx_close(ctx); // Business still holds reference, should be able to release it http2_stream_close(stream); // No crash should occur } TEST_F(LIBHTTP2, StressTest) { const int NUM_STREAMS = 1024; std::atomic server_processed(0); std::atomic client_completed(0); std::atomic test_completed(false); std::thread server_thread([this, NUM_STREAMS, &server_processed, &test_completed]() { struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake auto start_time = std::chrono::steady_clock::now(); int ret = 0; while (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(5)) { struct pollfd pfd = {server_sock, POLLIN, 0}; int poll_ret = poll(&pfd, 1, 10); if (poll_ret == 0) { continue; } ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Server handshake failed"; std::vector streams; start_time = std::chrono::steady_clock::now(); while (!test_completed && std::chrono::steady_clock::now() - start_time < std::chrono::seconds(30)) { struct pollfd pfd = {server_sock, POLLIN, 0}; poll(&pfd, 1, 10); struct http2_poll_item items[64]; int count = 0; http2_ctx_poll(ctx, items, 64, &count); for (int i = 0; i < count; i++) { if (items[i].stream == nullptr && items[i].readable) { struct http2_stream *stream = http2_ctx_accept_stream(ctx); if (stream) { streams.push_back(stream); } } else if (items[i].stream && items[i].readable) { struct http2_stream *stream = items[i].stream; uint8_t buf[1024]; while (http2_stream_read_body(stream, buf, sizeof(buf)) > 0) ; if (http2_stream_is_end(stream)) { char response[256]; int response_len = snprintf(response, sizeof(response), "Echo %d", http2_stream_get_id(stream)); char content_length[32]; snprintf(content_length, sizeof(content_length), "%d", response_len); struct http2_header_pair headers[] = {{"content-type", "text/plain"}, {"content-length", content_length}}; http2_stream_set_response(stream, 200, headers, 2); http2_stream_write_body(stream, (const uint8_t *)response, response_len, 1); server_processed++; } } if (items[i].stream) { http2_stream_put(items[i].stream); } } } for (auto stream : streams) { http2_stream_close(stream); } http2_ctx_close(ctx); }); std::thread client_thread([this, NUM_STREAMS, &client_completed, &test_completed]() { usleep(50000); struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL); ASSERT_NE(ctx, nullptr); // Handshake auto start_time = std::chrono::steady_clock::now(); int ret = 0; while (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(5)) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, 10); ret = http2_ctx_handshake(ctx); if (ret == 1) break; if (ret < 0) break; } ASSERT_EQ(ret, 1) << "Client handshake failed"; std::vector streams; streams.reserve(NUM_STREAMS); std::set completed_ids; auto process_events = [&](int timeout_ms) { struct pollfd pfd = {client_sock, POLLIN, 0}; poll(&pfd, 1, timeout_ms); struct http2_poll_item items[64]; int count = 0; http2_ctx_poll(ctx, items, 64, &count); for (int i = 0; i < count; i++) { if (items[i].stream && items[i].readable) { struct http2_stream *stream = items[i].stream; uint8_t buf[1024]; while (http2_stream_read_body(stream, buf, sizeof(buf)) > 0) ; if (http2_stream_is_end(stream)) { int id = http2_stream_get_id(stream); if (completed_ids.find(id) == completed_ids.end()) { completed_ids.insert(id); client_completed++; } } } if (items[i].stream) { http2_stream_put(items[i].stream); } } }; for (int i = 0; i < NUM_STREAMS; i++) { struct http2_stream *stream = http2_stream_new(ctx); if (stream) { streams.push_back(stream); char path[64]; snprintf(path, sizeof(path), "/stream%d", i); char body[64]; int body_len = snprintf(body, sizeof(body), "Req %d", i); struct http2_header_pair headers[] = {{"content-type", "text/plain"}, {NULL, NULL}}; http2_stream_set_request(stream, "POST", path, NULL, headers); http2_stream_write_body(stream, (const uint8_t *)body, body_len, 1); } // Process events periodically to prevent deadlock/buffer overflow if (i % 10 == 0) { process_events(0); } } ASSERT_EQ(streams.size(), NUM_STREAMS); start_time = std::chrono::steady_clock::now(); while (client_completed < NUM_STREAMS && std::chrono::steady_clock::now() - start_time < std::chrono::seconds(30)) { process_events(10); } EXPECT_EQ(client_completed, NUM_STREAMS); for (auto stream : streams) { http2_stream_close(stream); } http2_ctx_close(ctx); test_completed = true; }); server_thread.join(); client_thread.join(); } ================================================ FILE: test/cases/test-local-domain.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "smartdns/dns_client.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" #include class LocalDomain : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST(LocalDomain, query) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; smartdns::TempFile hosts_file; std::string listen_url = "udp://"; listen_url += DNS_MDNS_IP; listen_url += ":" + std::to_string(DNS_MDNS_PORT); server_upstream1.Start(listen_url.c_str(), [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[][4] = {{1, 2, 3, 4}}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); } else { return smartdns::SERVER_REQUEST_ERROR; } return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "102:304:500::1", 60, 100); hosts_file.Write("1.2.3.1 pc\n"); hosts_file.Write("1.2.3.2 phone\n"); hosts_file.Write("1.2.3.3 router\n"); std::string conf = R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no local-domain lan # mdns-lookup yes )"""; conf += "hosts-file " + hosts_file.GetPath() + "\n"; conf += "\n"; server.Start(conf); smartdns::Client client; ASSERT_TRUE(client.Query("b.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); ASSERT_TRUE(client.Query("pc A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "pc"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.1"); ASSERT_TRUE(client.Query("phone.lan A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "phone.lan"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 59); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.2"); ASSERT_TRUE(client.Query("router.lan AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); } TEST(LocalDomain, ptr) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; smartdns::TempFile hosts_file; std::string listen_url = "udp://"; listen_url += DNS_MDNS_IP; listen_url += ":" + std::to_string(DNS_MDNS_PORT); server_upstream1.Start(listen_url.c_str(), [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype != DNS_T_PTR) { return smartdns::SERVER_REQUEST_SOA; } dns_add_PTR(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, "host.local"); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_ERROR; }); hosts_file.Write("1.2.3.1 pc\n"); hosts_file.Write("1.2.3.2 phone\n"); hosts_file.Write("1.2.3.3 router\n"); std::string conf = R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no local-domain lan )"""; conf += "hosts-file " + hosts_file.GetPath() + "\n"; conf += "\n"; server.Start(conf); smartdns::Client client; ASSERT_TRUE(client.Query("-x 1.2.3.1", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "1.3.2.1.in-addr.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "pc."); } ================================================ FILE: test/cases/test-mdns.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "smartdns/dns_client.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" #include class mDNS : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST(mDNS, query) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; std::string listen_url = "udp://"; listen_url += DNS_MDNS_IP; listen_url += ":" + std::to_string(DNS_MDNS_PORT); server_upstream1.Start(listen_url.c_str(), [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[][4] = {{1, 2, 3, 4}}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]); } else { return smartdns::SERVER_REQUEST_ERROR; } return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "102:304:500::1", 60, 100); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no mdns-lookup yes group-begin test address - )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); ASSERT_TRUE(client.Query("host A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "host"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "host.local."); EXPECT_EQ(client.GetAnswer()[1].GetName(), "host.local"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("host A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "host"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 59); EXPECT_EQ(client.GetAnswer()[0].GetData(), "host.local."); EXPECT_EQ(client.GetAnswer()[1].GetName(), "host.local"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("host AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "host"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "host.local."); EXPECT_EQ(client.GetAnswer()[1].GetName(), "host.local"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "102:304:500::1"); ASSERT_TRUE(client.Query("host.local A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "host.local"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST(mDNS, ptr) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; std::string listen_url = "udp://"; listen_url += DNS_MDNS_IP; listen_url += ":" + std::to_string(DNS_MDNS_PORT); server_upstream1.Start(listen_url.c_str(), [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype != DNS_T_PTR) { return smartdns::SERVER_REQUEST_SOA; } dns_add_PTR(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, "host.local"); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_ERROR; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no mdns-lookup yes )"""); smartdns::Client client; ASSERT_TRUE(client.Query("-x 127.0.0.9", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "9.0.0.127.in-addr.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "host.local."); } ================================================ FILE: test/cases/test-mock-server.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" TEST(MockServer, query_fail) { smartdns::MockServer server; smartdns::Client client; server.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { request->response_data_len = 0; return smartdns::SERVER_REQUEST_ERROR; }); ASSERT_TRUE(client.Query("example.com", 61053)); std::cout << client.GetResult() << std::endl; EXPECT_EQ(client.GetStatus(), "SERVFAIL"); } TEST(MockServer, soa) { smartdns::MockServer server; smartdns::Client client; server.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); ASSERT_TRUE(client.Query("example.com", 61053)); std::cout << client.GetResult() << std::endl; EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); } TEST(MockServer, noerror) { smartdns::MockServer server; smartdns::Client client; server.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_OK; }); ASSERT_TRUE(client.Query("example.com", 61053)); std::cout << client.GetResult() << std::endl; EXPECT_EQ(client.GetStatus(), "NOERROR"); } ================================================ FILE: test/cases/test-nameserver.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" class NameServer : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(NameServer, cname) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "9.10.11.12", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream1.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:63053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8", 611); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 server 127.0.0.1:62053 -group g1 -exclude-default-group server 127.0.0.1:63053 -group g2 -exclude-default-group nameserver /a.com/g1 nameserver /b.com/g2 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("c.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "c.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "9.10.11.12"); } ================================================ FILE: test/cases/test-perf.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" #include class Perf : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(Perf, no_speed_check) { smartdns::MockServer server_upstream; smartdns::Server server; if (smartdns::IsCommandExists("dnsperf") == false) { printf("dnsperf not found, skip test, please install dnsperf first.\n"); GTEST_SKIP(); } server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[4] = {1, 2, 3, 4}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none log-level error socket-buff-size 1M )"""); std::string file = "/tmp/smartdns-perftest-domain.list" + smartdns::GenerateRandomString(5); std::string cmd = "dnsperf -p 60053 -b 1024"; cmd += " -d "; cmd += file; std::ofstream ofs(file); ASSERT_TRUE(ofs.is_open()); Defer { ofs.close(); unlink(file.c_str()); }; for (int i = 0; i < 100000; i++) { std::string domain = smartdns::GenerateRandomString(10); domain += "."; domain += smartdns::GenerateRandomString(3); if (random() % 2 == 0) { domain += " A"; } else { domain += " AAAA"; } domain += "\n"; ofs.write(domain.c_str(), domain.length()); ofs.flush(); } system(cmd.c_str()); } ================================================ FILE: test/cases/test-ping.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/fast_ping.h" #include "include/utils.h" #include "server.h" #include "smartdns/tlog.h" #include "gtest/gtest.h" class Ping : public ::testing::Test { protected: virtual void SetUp() { EXPECT_EQ(fast_ping_init(), 0); loglevel = tlog_getlevel(); tlog_setlevel(TLOG_DEBUG); } virtual void TearDown() { fast_ping_exit(); tlog_setlevel(loglevel); } private: tlog_level loglevel; }; void ping_result_callback(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result, struct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv, int error, void *userptr) { int *count = (int *)userptr; if (result == PING_RESULT_RESPONSE) { *count = 1; tlog(TLOG_INFO, "ping to %s succeeded, seq=%d, ttl=%d", host, seqno, ttl); } } TEST_F(Ping, icmp) { struct ping_host_struct *ping_host; int count = 0; if (smartdns::IsICMPAvailable() == false) { tlog(TLOG_INFO, "ICMP is not available, skip this test."); GTEST_SKIP(); return; } ping_host = fast_ping_start(PING_TYPE_ICMP, "127.0.0.1", 1, 1, 200, ping_result_callback, &count); ASSERT_NE(ping_host, nullptr); usleep(10000); fast_ping_stop(ping_host); EXPECT_EQ(count, 1); } TEST_F(Ping, tcp) { struct ping_host_struct *ping_host; int count = 0; ping_host = fast_ping_start(PING_TYPE_TCP, "127.0.0.1:1", 1, 1, 200, ping_result_callback, &count); ASSERT_NE(ping_host, nullptr); usleep(10000); fast_ping_stop(ping_host); EXPECT_EQ(count, 1); } TEST_F(Ping, tcp_syn) { struct ping_host_struct *ping_host; int count = 0; /* Test TCP SYN ping - may not work in all environments */ ping_host = fast_ping_start(PING_TYPE_TCP_SYN, "127.0.0.1:53", 5, 1000, 1000, ping_result_callback, &count); if (ping_host == nullptr) { tlog(TLOG_INFO, "TCP SYN ping not available (need root/CAP_NET_RAW), skip this test."); GTEST_SKIP(); return; } usleep(10000); fast_ping_stop(ping_host); EXPECT_GT(count, 0); } TEST_F(Ping, tcp_syn_v6) { struct ping_host_struct *ping_host; int count = 0; /* Test TCP SYN ping - may not work in all environments */ ping_host = fast_ping_start(PING_TYPE_TCP_SYN, "[::1]:443", 2, 1000, 1000, ping_result_callback, &count); if (ping_host == nullptr) { tlog(TLOG_INFO, "TCP SYN ping not available (need root/CAP_NET_RAW), skip this test."); GTEST_SKIP(); return; } usleep(1000000); fast_ping_stop(ping_host); EXPECT_GT(count, 0); } TEST_F(Ping, tcp_syn_concurrent) { struct ping_host_struct *ping_host1; struct ping_host_struct *ping_host2; int count1 = 0, count2 = 0; /* Test concurrent TCP SYN pings to different servers */ ping_host1 = fast_ping_start(PING_TYPE_TCP_SYN, "127.0.0.1:22", 1, 1, 500, ping_result_callback, &count1); if (ping_host1 == nullptr) { tlog(TLOG_INFO, "TCP SYN ping not available, skip this test."); GTEST_SKIP(); return; } tlog(TLOG_INFO, "First ping started, now starting second..."); /* Use Alibaba DNS server (accessible in China) */ ping_host2 = fast_ping_start(PING_TYPE_TCP_SYN, "127.0.0.2:53", 1, 1, 500, ping_result_callback, &count2); ASSERT_NE(ping_host2, nullptr); tlog(TLOG_INFO, "Both pings started, waiting 200ms..."); usleep(200000); /* Wait 200ms for responses */ tlog(TLOG_INFO, "Wait complete, stopping pings..."); fast_ping_stop(ping_host1); fast_ping_stop(ping_host2); usleep(50000); /* Brief wait for cleanup */ tlog(TLOG_INFO, "TCP SYN concurrent ping test completed, count1=%d, count2=%d", count1, count2); /* At least one should succeed (localhost SSH should be reachable) */ EXPECT_GE(count1 + count2, 1); } void fake_ping_result_callback(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result, struct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv, int error, void *userptr) { if (result == PING_RESULT_RESPONSE) { int *count = (int *)userptr; double rtt = tv->tv_sec * 1000.0 + tv->tv_usec / 1000.0; tlog(TLOG_INFO, "from %15s: seq=%d ttl=%d time=%.3f\n", host, seqno, ttl, rtt); *count = (int)rtt; } } TEST_F(Ping, fake_icmp) { struct ping_host_struct *ping_host; int count = 0; fast_ping_fake_ip_add(PING_TYPE_ICMP, "1.2.3.4", 60, 5); ping_host = fast_ping_start(PING_TYPE_ICMP, "1.2.3.4", 1, 1000, 200, fake_ping_result_callback, &count); ASSERT_NE(ping_host, nullptr); usleep(100000); fast_ping_stop(ping_host); EXPECT_GE(count, 5); } ================================================ FILE: test/cases/test-ptr.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class Ptr : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(Ptr, query) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } if (request->qtype == DNS_T_PTR) { dns_add_PTR(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 30, "my-hostname"); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("4.3.2.1.in-addr.arpa PTR", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "4.3.2.1.in-addr.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "my-hostname."); } TEST_F(Ptr, address_expand_ptr) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none expand-ptr-from-address yes address /a.com/10.11.12.13 address /a.com/64:ff9b::1010:1010 address /pi.local/192.168.1.1 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("13.12.11.10.in-addr.arpa PTR", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "13.12.11.10.in-addr.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "a.com."); ASSERT_TRUE(client.Query("0.1.0.1.0.1.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa PTR", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "0.1.0.1.0.1.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "a.com."); ASSERT_TRUE(client.Query("-x 192.168.1.1", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "1.1.168.192.in-addr.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "pi.local."); } TEST_F(Ptr, smartdns) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } if (request->qtype == DNS_T_PTR) { dns_add_PTR(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 30, "my-hostname"); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 server-name my-server dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("1.0.0.127.in-addr.arpa PTR", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "1.0.0.127.in-addr.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "my-server."); } TEST_F(Ptr, private_soa) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } if (request->qtype == DNS_T_PTR) { dns_add_PTR(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 30, "my-hostname"); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("4.3.168.192.in-addr.arpa PTR", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "4.3.168.192.in-addr.arpa"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 600); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } TEST_F(Ptr, private_nameserver) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } if (request->qtype == DNS_T_PTR) { dns_add_PTR(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 30, "my-hostname"); request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 -group test-server nameserver /168.192.in-addr.arpa/test-server dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("4.3.168.192.in-addr.arpa PTR", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "4.3.168.192.in-addr.arpa"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "PTR"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "my-hostname."); } ================================================ FILE: test/cases/test-qtype-soa.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" class QtypeSOA : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(QtypeSOA, AAAA_HTTPS) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 force-qtype-SOA 28,65 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com -t HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("a.com A", 60053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); } TEST_F(QtypeSOA, AAAA_Except) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no force-qtype-SOA 28 address /a.com/- )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); } TEST_F(QtypeSOA, force_AAAA_SOA_Except) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no force-AAAA-SOA yes address /a.com/- )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); } TEST_F(QtypeSOA, force_AAAA_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none force-AAAA-SOA yes )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } TEST_F(QtypeSOA, bind_force_AAAA_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""( bind [::]:60053 bind [::]:60153 -force-aaaa-soa server 127.0.0.1:61053 speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); ASSERT_TRUE(client.Query("a.com A", 60153)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com AAAA", 60153)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } TEST_F(QtypeSOA, HTTPS_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; std::map qid_map; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_HTTPS) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; struct dns_rr_nested svcparam_buffer; dns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, "a.com"); const char alph[] = "\x02h2\x05h3-19"; int alph_len = sizeof(alph) - 1; dns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len); dns_HTTPS_add_port(&svcparam_buffer, 443); unsigned char add_v4[] = {1, 2, 3, 4}; unsigned char *addr[1] = {add_v4}; dns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1); unsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00}; dns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech)); unsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; addr[0] = add_v6; dns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1); dns_add_HTTPS_end(&svcparam_buffer); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-console yes dualstack-ip-selection no speed-check-mode none address /a.com/# log-level debug cache-persist no)"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com HTTPS", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } ================================================ FILE: test/cases/test-rule.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "include/utils.h" #include "server.h" #include "smartdns/dns.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class Rule : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(Rule, Match) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /a.com/5.6.7.8 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("a.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("aa.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "aa.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Rule, PrefixWildcardMatch) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /*a.com/5.6.7.8 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("a.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("aa.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "aa.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("ab.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "ab.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Rule, SubDomainMatchOnly) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /*.a.com/5.6.7.8 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("aa.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "aa.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Rule, RootDomainMatchOnly) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /-.a.com/5.6.7.8 address /b.com/3.4.5.6 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("a.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("b.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("ba.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "ba.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("b.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "3.4.5.6"); } TEST_F(Rule, AAAA_SOA) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address /-.a.com/#6 address /*.b.com/#6 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("a.a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); ASSERT_TRUE(client.Query("a.b.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("b.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); } TEST_F(Rule, root) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700); return smartdns::SERVER_REQUEST_OK; } else if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none address #6 address /-.a.com/-6 address )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::102:304"); ASSERT_TRUE(client.Query("a.a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_TRUE(client.Query("b.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NOERROR"); } TEST_F(Rule, root_and_sub) { smartdns::Server server; smartdns::MockServer server_upstream; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "9.9.9.9", 700); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 log-level debug speed-check-mode none address /q.a.com/1.2.3.4 address /-.a.com/4.5.6.7 address /*.a.com/7.8.9.10 )"""); smartdns::Client client; // 1. q.a.com should be 1.2.3.4 ASSERT_TRUE(client.Query("q.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); // 2. a.com should be 4.5.6.7 (matching -.a.com) ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetData(), "4.5.6.7"); // 3. other.a.com should be 7.8.9.10 (matching *.a.com) ASSERT_TRUE(client.Query("other.a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetData(), "7.8.9.10"); } ================================================ FILE: test/cases/test-same-pending-query.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class SamePending : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(SamePending, pending) { smartdns::MockServer server_upstream; smartdns::Server server; std::map qid_map; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { std::string domain = request->domain; if (qid_map.find(request->packet->head.id) != qid_map.end()) { qid_map[request->packet->head.id]++; usleep(5000); } else { qid_map[request->packet->head.id] = 1; usleep(20000); } if (request->domain.length() == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (request->qtype == DNS_T_A) { unsigned char addr[4] = {1, 2, 3, 4}; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr); } else if (request->qtype == DNS_T_AAAA) { unsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr); } else { return smartdns::SERVER_REQUEST_ERROR; } request->response_packet->head.rcode = DNS_RC_NOERROR; return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 cache-size 0 speed-check-mode none log-level error )"""); std::vector threads; for (int i = 0; i < 5; i++) { auto t = std::thread([&]() { for (int j = 0; j < 10; j++) { smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } }); threads.push_back(std::move(t)); } for (auto &t : threads) { t.join(); } EXPECT_LT(qid_map.size(), 80); } ================================================ FILE: test/cases/test-server.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" class Server : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(Server, all_unreach) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); EXPECT_EQ(request->domain, "e.com"); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "2001::", 128, 10000); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server tls://255.255.255.255 server https://255.255.255.255 server tcp://255.255.255.255 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; EXPECT_EQ(client.GetStatus(), "SERVFAIL"); EXPECT_EQ(client.GetAnswerNum(), 0); /* server should not crash */ ASSERT_TRUE(client.Query("a.com +tcp", 60053)); std::cout << client.GetResult() << std::endl; EXPECT_EQ(client.GetStatus(), "SERVFAIL"); EXPECT_EQ(client.GetAnswerNum(), 0); } TEST_F(Server, one_nxdomain) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream1; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } usleep(50000); smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream1.Start("udp://0.0.0.0:62053", [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "2001::", 128, 10000); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 server 127.0.0.1:62053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Server, retry_no_result_with_NOERROR) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream1; smartdns::Server server; int count = 0; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } if (count++ < 2) { dns_add_domain(request->response_packet, request->domain.c_str(), request->qtype, request->qclass); request->response_packet->head.tc = 1; return smartdns::SERVER_REQUEST_OK; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Server, retry_no_response) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream1; smartdns::Server server; int count = 0; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { count++; return smartdns::SERVER_REQUEST_NO_RESPONSE; }); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "SERVFAIL"); EXPECT_GE(client.GetQueryTime(), 1500); EXPECT_GE(count, 4); } TEST_F(Server, max_queries) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream1; smartdns::Server server; int count = 0; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); sleep(1); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 128, 10); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no max-query-limit 2 )"""); std::vector threads; int success_num = 0; int refused_num = 0; for (int i = 0; i < 5; i++) { auto t = std::thread([&]() { smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); if (client.GetStatus() == "NOERROR") { success_num++; EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } else if (client.GetStatus() == "REFUSED") { refused_num++; } else { FAIL(); } }); threads.push_back(std::move(t)); } for (auto &t : threads) { t.join(); } EXPECT_EQ(success_num, 2); EXPECT_EQ(refused_num, 3); for (int i = 0; i < 5; i++) { smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); EXPECT_EQ(client.GetStatus(), "NOERROR"); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } } TEST_F(Server, interface) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "2001::", 128, 10000); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 -interface lo )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Server, refused) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream1; smartdns::Server server; int count = 0; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { request->response_packet->head.rcode = DNS_RC_REFUSED; dns_add_domain(request->response_packet, request->domain.c_str(), request->qtype, request->qclass); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "REFUSED"); EXPECT_LT(client.GetQueryTime(), 100); } TEST_F(Server, fallback) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "2001::", 128, 10000); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 -fallback server 127.0.0.1:61054 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_GE(client.GetQueryTime(), 1000); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Server, fallback_group) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "2001::", 128, 10000); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 -e -group fallback server 127.0.0.1:61054 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_GE(client.GetQueryTime(), 1000); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Server, groups) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::MockServer server_upstream3; smartdns::Server server; server_upstream1.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:61054", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.5", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream3.Start("udp://0.0.0.0:61055", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.6", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 128, 10); server.MockPing(PING_TYPE_ICMP, "1.2.3.5", 128, 10); server.MockPing(PING_TYPE_ICMP, "1.2.3.6", 128, 10); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 server 127.0.0.1:61054 -e -group a -group b server 127.0.0.1:61055 -e -group c -group d nameserver /a.com/a nameserver /b.com/b nameserver /c.com/c nameserver /d.com/d nameserver /e.com/unknown )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.5"); ASSERT_TRUE(client.Query("b.com", 60053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.5"); ASSERT_TRUE(client.Query("c.com", 60053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetName(), "c.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.6"); ASSERT_TRUE(client.Query("d.com", 60053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetName(), "d.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.6"); ASSERT_TRUE(client.Query("e.com", 60053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetName(), "e.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("f.com", 60053)); ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetAnswer()[0].GetName(), "f.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Server, repeat_group) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { static int count = 0; if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } count++; smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); if (count > 1) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.5", 611); } return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "2001::", 128, 10000); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 -e -group a -group a nameserver /a.com/a )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); usleep(100000); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(Server, bad_block_ip) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::MockServer server_upstream3; smartdns::Server server; server_upstream1.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "127.0.0.1", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:61054", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "::", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream3.Start("udp://0.0.0.0:61055", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } usleep(100000); smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.6", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 128, 10); server.MockPing(PING_TYPE_ICMP, "1.2.3.5", 128, 10); server.MockPing(PING_TYPE_ICMP, "1.2.3.6", 128, 10); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 server 127.0.0.1:61054 server 127.0.0.1:61055 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.6"); } TEST_F(Server, bad_block_ip_no_check_speed) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::MockServer server_upstream3; smartdns::Server server; server_upstream1.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "127.0.0.1", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream2.Start("udp://0.0.0.0:61054", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "::", 611); return smartdns::SERVER_REQUEST_OK; }); server_upstream3.Start("udp://0.0.0.0:61055", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } usleep(100000); smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.6", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 128, 10); server.MockPing(PING_TYPE_ICMP, "1.2.3.5", 128, 10); server.MockPing(PING_TYPE_ICMP, "1.2.3.6", 128, 10); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 server 127.0.0.1:61054 server 127.0.0.1:61055 speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.6"); } TEST_F(Server, case_insensitive) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } usleep(100000); smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 611); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 128, 1000); server.Start(R"""(bind [::]:60053 bind-tcp [::]:60053 server 127.0.0.1:61053 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); ASSERT_TRUE(client.Query("A.cOm", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LE(client.GetQueryTime(), 5); EXPECT_EQ(client.GetAnswer()[0].GetName(), "A.cOm"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } ================================================ FILE: test/cases/test-speed-check.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class SpeedCheck : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(SpeedCheck, response_mode) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 response-mode first-ping domain-rules /a.com/ -r fastest-response )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); if (smartdns::IsICMPAvailable()) { EXPECT_GT(client.GetQueryTime(), 100); } EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 10); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8"); } TEST_F(SpeedCheck, none) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 40); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 40); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8"); } TEST_F(SpeedCheck, domain_rules_none) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 domain-rules /a.com/ -c none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); if (smartdns::IsICMPAvailable()) { EXPECT_GT(client.GetQueryTime(), 200); } EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8"); } TEST_F(SpeedCheck, only_ping) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 1200); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); } TEST_F(SpeedCheck, no_ping_fallback_tcp) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 1000); server.MockPing(PING_TYPE_TCP, "5.6.7.8:80", 60, 100); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping,tcp:80 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 500); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); } TEST_F(SpeedCheck, tcp_faster_than_ping) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 300); server.MockPing(PING_TYPE_TCP, "5.6.7.8:80", 60, 10); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping,tcp:80 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 500); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); } TEST_F(SpeedCheck, fastest_ip) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 110); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode ping dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 200); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); usleep(220 * 1000); ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8"); } TEST_F(SpeedCheck, unreach_best_ipv4) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server_upstream2.Start("udp://0.0.0.0:62053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "9.10.11.12"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 10000); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 10000); server.MockPing(PING_TYPE_ICMP, "9.10.11.12", 60, 10000); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 server 127.0.0.1:62053 speed-check-mode ping dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 1200); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(SpeedCheck, unreach_best_ipv6) { smartdns::MockServer server_upstream; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server_upstream2.Start("udp://0.0.0.0:62053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_AAAA) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::3"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 10000); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 10000); server.MockPing(PING_TYPE_ICMP, "2001:db8::3", 60, 10000); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 server 127.0.0.1:62053 speed-check-mode ping dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 1200); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_GT(client.GetAnswer()[0].GetTTL(), 597); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::2"); } TEST_F(SpeedCheck, global_none_rule_check) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 150); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none domain-rules /b.com/ -c ping )"""); smartdns::Client client; ASSERT_TRUE(client.Query("b.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_GT(client.GetQueryTime(), 50); EXPECT_EQ(client.GetAnswer()[0].GetName(), "b.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); ASSERT_TRUE(client.Query("a.com", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_LT(client.GetQueryTime(), 20); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); EXPECT_EQ(client.GetAnswer()[1].GetData(), "5.6.7.8"); } ================================================ FILE: test/cases/test-srv.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class SRV : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(SRV, query) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_SRV) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; dns_add_SRV(packet, DNS_RRS_AN, request->domain.c_str(), 603, 1, 1, 443, "www.example.com"); dns_add_SRV(packet, DNS_RRS_AN, request->domain.c_str(), 603, 1, 1, 443, "www1.example.com"); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("_ldap._tcp.local.com SRV", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 2); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "_ldap._tcp.local.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 603); EXPECT_EQ(client.GetAnswer()[0].GetType(), "SRV"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 1 443 www.example.com."); } TEST_F(SRV, match) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_SRV) { return smartdns::SERVER_REQUEST_SOA; } struct dns_packet *packet = request->response_packet; dns_add_SRV(packet, DNS_RRS_AN, request->domain.c_str(), 603, 1, 1, 443, "www.example.com"); dns_add_SRV(packet, DNS_RRS_AN, request->domain.c_str(), 603, 1, 1, 443, "www1.example.com"); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 srv-record /_ldap._tcp.local.com/www.a.com,443,1,1 srv-record /_ldap._tcp.local.com/www1.a.com,443,1,1 srv-record /_ldap._tcp.local.com/www2.a.com,443,1,1 speed-check-mode none )"""); smartdns::Client client; ASSERT_TRUE(client.Query("_ldap._tcp.local.com SRV", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 3); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "_ldap._tcp.local.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600); EXPECT_EQ(client.GetAnswer()[0].GetType(), "SRV"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1 1 443 www.a.com."); } ================================================ FILE: test/cases/test-stress.cc ================================================ #include "server.h" #include "smartdns/dns.h" #include "gtest/gtest.h" #include #include #include #include #include #include #include #include #include #include // Helper function to get environment variable with default value int get_env_int(const char* name, int default_value) { const char* value = std::getenv(name); if (value) { return std::atoi(value); } return default_value; } // Simple UDP DNS query function bool udp_dns_query(const std::string& domain, int port) { int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) return false; struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); // Build simple DNS query for A record unsigned char query[256]; memset(query, 0, sizeof(query)); // Random ID query[0] = rand() % 256; query[1] = rand() % 256; // Flags: recursion desired query[2] = 0x01; query[3] = 0x00; // QDCOUNT = 1 query[4] = 0x00; query[5] = 0x01; // Encode domain name int pos = 12; size_t start = 0; size_t dot_pos = domain.find('.'); while (dot_pos != std::string::npos) { std::string label = domain.substr(start, dot_pos - start); query[pos++] = label.size(); memcpy(&query[pos], label.c_str(), label.size()); pos += label.size(); start = dot_pos + 1; dot_pos = domain.find('.', start); } std::string label = domain.substr(start); query[pos++] = label.size(); memcpy(&query[pos], label.c_str(), label.size()); pos += label.size(); query[pos++] = 0; // null terminator // QTYPE: A (1) query[pos++] = 0x00; query[pos++] = 0x01; // QCLASS: IN (1) query[pos++] = 0x00; query[pos++] = 0x01; // Send query if (sendto(sock, query, pos, 0, (struct sockaddr*)&addr, sizeof(addr)) < 0) { close(sock); return false; } // Receive response unsigned char response[512]; socklen_t addr_len = sizeof(addr); int recv_len = recvfrom(sock, response, sizeof(response), 0, (struct sockaddr*)&addr, &addr_len); close(sock); if (recv_len < 12) return false; // Check RCODE (last 4 bits of flags) if ((response[3] & 0x0F) == 0) return true; // NOERROR return false; } // Protocol stress test configuration struct ProtocolConfig { std::string name; std::string bind_config; std::string server_config; std::string upstream_bind_config; }; class Stress : public ::testing::TestWithParam { protected: void SetUp() override { // Common setup if needed } void TearDown() override { // Common cleanup if needed } }; // Define protocol configurations const ProtocolConfig protocols[] = { { "UDP", "bind [::]:61053", "server udp://127.0.0.1:60053", "bind [::]:60053" }, { "TCP", "bind [::]:61053", "server tcp://127.0.0.1:60053", "bind-tcp [::]:60053" }, { "TLS", "bind [::]:61053", "server tls://127.0.0.1:60053 -no-check-certificate", "bind-tls [::]:60053" }, { "HTTP2", "bind [::]:61053", "server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2", "bind-https [::]:60053 -alpn h2" }, { "HTTP1_1", "bind [::]:61053", "server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn http/1.1", "bind-https [::]:60053 -alpn http/1.1" } }; // Test stress for each protocol: 100 clients, each making 100 queries TEST_P(Stress, Query) { const auto& config = GetParam(); smartdns::Server upstream_server; smartdns::Server main_server; // Start upstream server (second layer) that returns fixed IP and mocks ping upstream_server.Start(config.upstream_bind_config + R"""( address /test.com/192.168.1.100 address /example.com/192.168.1.101 address /domain.com/192.168.1.102 )"""); // Mock ping responses for the IPs main_server.MockPing(PING_TYPE_ICMP, "192.168.1.100", 60, 10); main_server.MockPing(PING_TYPE_ICMP, "192.168.1.101", 60, 5); main_server.MockPing(PING_TYPE_ICMP, "192.168.1.102", 60, 20); // Start main server that forwards to upstream via specified protocol main_server.Start(config.bind_config + "\n" + config.server_config + R"""( cache-size 0 speed-check-mode ping )"""); // Wait for servers to be ready std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::vector client_threads; std::atomic total_queries{0}; std::atomic success_count{0}; std::atomic failure_count{0}; std::atomic stop_all_tasks{false}; // Flag to control all tasks exit const int num_clients = get_env_int("SMARTDNS_STRESS_CLIENTS", 1); const int queries_per_client = get_env_int("SMARTDNS_STRESS_QUERIES", 200); auto start_time = std::chrono::steady_clock::now(); // Launch 100 client threads, each making 100 queries for (int client_id = 0; client_id < num_clients; client_id++) { client_threads.emplace_back([client_id, &total_queries, &success_count, &failure_count, &stop_all_tasks, queries_per_client]() { for (int query_id = 0; query_id < queries_per_client; query_id++) { // Check if stop flag is set, terminate all tasks if (stop_all_tasks.load()) { return; } std::string domain; // Rotate through different domains to test various responses switch (query_id % 3) { case 0: domain = "test.com"; break; case 1: domain = "example.com"; break; case 2: domain = "domain.com"; break; } total_queries++; if (udp_dns_query(domain, 61053)) { success_count++; } else { failure_count++; stop_all_tasks.store(true); // Set flag to stop all tasks return; } } }); } // Wait for all client threads to complete for (auto& t : client_threads) { t.join(); } auto end_time = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end_time - start_time); int expected_total = num_clients * queries_per_client; double qps = (expected_total * 1000.0) / duration.count(); std::cout << config.name << " Stress Test Results:" << std::endl; std::cout << " Total Queries: " << total_queries.load() << " (expected: " << expected_total << ")" << std::endl; std::cout << " Success: " << success_count.load() << std::endl; std::cout << " Failure: " << failure_count.load() << std::endl; std::cout << " Duration: " << duration.count() << "ms" << std::endl; std::cout << " QPS: " << qps << std::endl; double success_rate = total_queries.load() > 0 ? (success_count.load() * 100.0 / total_queries.load()) : 0.0; std::cout << " Success Rate: " << success_rate << "%" << std::endl; // Assertions EXPECT_FALSE(stop_all_tasks.load()); // No failures should occur, all tasks should complete EXPECT_EQ(total_queries.load(), expected_total); EXPECT_EQ(success_count.load(), expected_total); EXPECT_EQ(failure_count.load(), 0); } // Instantiate the test for each protocol INSTANTIATE_TEST_SUITE_P(, Stress, ::testing::ValuesIn(protocols), [](const ::testing::TestParamInfo& info) { return info.param.name; }); // filter to run specific tests // ./test.bin --gtest_filter="Stress.Query/UDP" // ./test.bin --gtest_filter="Stress.Query/TCP" ================================================ FILE: test/cases/test-subnet.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "smartdns/util.h" #include "gtest/gtest.h" #include class SubNet : public ::testing::Test { protected: virtual void SetUp() {} virtual void TearDown() {} }; TEST_F(SubNet, pass_subnet) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } struct dns_opt_ecs ecs; struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int has_ecs = 0; rr_count = 0; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return smartdns::SERVER_REQUEST_ERROR; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } has_ecs = 1; break; } if (has_ecs == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.family != 1) { return smartdns::SERVER_REQUEST_ERROR; } if (memcmp(ecs.addr, "\x08\x08\x08\x00", 4) != 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.source_prefix != 24) { return smartdns::SERVER_REQUEST_ERROR; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A +subnet=8.8.8.8/24", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(SubNet, conf) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_A) { return smartdns::SERVER_REQUEST_SOA; } struct dns_opt_ecs ecs; struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int has_ecs = 0; rr_count = 0; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return smartdns::SERVER_REQUEST_ERROR; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } has_ecs = 1; break; } if (has_ecs == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.family != DNS_OPT_ECS_FAMILY_IPV4) { return smartdns::SERVER_REQUEST_ERROR; } if (memcmp(ecs.addr, "\x08\x08\x08\x00", 4) != 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.source_prefix != 24) { return smartdns::SERVER_REQUEST_ERROR; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no edns-client-subnet 8.8.8.8/24 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "1.2.3.4"); } TEST_F(SubNet, conf_v6) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_AAAA) { return smartdns::SERVER_REQUEST_SOA; } struct dns_opt_ecs ecs; struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int has_ecs = 0; rr_count = 0; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return smartdns::SERVER_REQUEST_ERROR; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } has_ecs = 1; break; } if (has_ecs == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.family != DNS_OPT_ECS_FAMILY_IPV6) { return smartdns::SERVER_REQUEST_ERROR; } if (memcmp(ecs.addr, "\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00", 16) != 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.source_prefix != 64) { return smartdns::SERVER_REQUEST_ERROR; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); return smartdns::SERVER_REQUEST_OK; }); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 70); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no edns-client-subnet ffff:ffff:ffff:ffff:ffff::/64 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::1"); } TEST_F(SubNet, v4_server_subnet_txt) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_TXT) { return smartdns::SERVER_REQUEST_SOA; } struct dns_opt_ecs ecs; struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int has_ecs = 0; rr_count = 0; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return smartdns::SERVER_REQUEST_ERROR; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } has_ecs = 1; break; } if (has_ecs == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.family != DNS_OPT_ECS_FAMILY_IPV4) { return smartdns::SERVER_REQUEST_ERROR; } if (memcmp(ecs.addr, "\x08\x08\x08\x00", 4) != 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.source_prefix != 24) { return smartdns::SERVER_REQUEST_ERROR; } dns_add_TXT(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 6, "hello world"); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 -subnet 8.8.8.8/24 -subnet-all-query-types dualstack-ip-selection no rr-ttl-min 0 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com TXT", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 6); EXPECT_EQ(client.GetAnswer()[0].GetType(), "TXT"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "\"hello world\""); } TEST_F(SubNet, v6_default_subnet_txt) { smartdns::MockServer server_upstream; smartdns::Server server; server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype != DNS_T_TXT) { return smartdns::SERVER_REQUEST_SOA; } struct dns_opt_ecs ecs; struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int has_ecs = 0; rr_count = 0; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return smartdns::SERVER_REQUEST_ERROR; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } has_ecs = 1; break; } if (has_ecs == 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.family != DNS_OPT_ECS_FAMILY_IPV6) { return smartdns::SERVER_REQUEST_ERROR; } if (memcmp(ecs.addr, "\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00", 16) != 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.source_prefix != 64) { return smartdns::SERVER_REQUEST_ERROR; } dns_add_TXT(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 6, "hello world"); return smartdns::SERVER_REQUEST_OK; }); server.Start(R"""(bind [::]:60053 server 127.0.0.1:61053 dualstack-ip-selection no rr-ttl-min 0 edns-client-subnet ffff:ffff:ffff:ffff:ffff::/64 )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com TXT", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 6); EXPECT_EQ(client.GetAnswer()[0].GetType(), "TXT"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "\"hello world\""); } TEST_F(SubNet, per_server) { smartdns::MockServer server_upstream1; smartdns::MockServer server_upstream2; smartdns::Server server; server_upstream1.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { struct dns_opt_ecs ecs; struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int has_ecs = 0; rr_count = 0; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return smartdns::SERVER_REQUEST_ERROR; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } has_ecs = 1; break; } if (has_ecs == 1) { return smartdns::SERVER_REQUEST_ERROR; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } if (request->qtype == DNS_T_AAAA) { struct dns_opt_ecs ecs; struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int has_ecs = 0; rr_count = 0; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return smartdns::SERVER_REQUEST_ERROR; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } has_ecs = 1; break; } if (has_ecs == 1) { return smartdns::SERVER_REQUEST_ERROR; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server_upstream2.Start("udp://0.0.0.0:62053", [&](struct smartdns::ServerRequestContext *request) { if (request->qtype == DNS_T_A) { struct dns_opt_ecs ecs; struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int has_ecs = 0; rr_count = 0; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return smartdns::SERVER_REQUEST_ERROR; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } has_ecs = 1; break; } if (has_ecs == 0) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4"); return smartdns::SERVER_REQUEST_OK; } if (ecs.family != DNS_OPT_ECS_FAMILY_IPV4) { return smartdns::SERVER_REQUEST_ERROR; } if (memcmp(ecs.addr, "\x08\x08\x08\x00", 4) != 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.source_prefix != 24) { return smartdns::SERVER_REQUEST_ERROR; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "5.6.7.8"); return smartdns::SERVER_REQUEST_OK; } if (request->qtype == DNS_T_AAAA) { struct dns_opt_ecs ecs; struct dns_rrs *rrs = NULL; int rr_count = 0; int i = 0; int ret = 0; int has_ecs = 0; rr_count = 0; rrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count); if (rr_count <= 0) { return smartdns::SERVER_REQUEST_ERROR; } for (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) { memset(&ecs, 0, sizeof(ecs)); ret = dns_get_OPT_ECS(rrs, &ecs); if (ret != 0) { continue; } has_ecs = 1; break; } if (has_ecs == 0) { smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::1"); return smartdns::SERVER_REQUEST_ERROR; } if (ecs.family != DNS_OPT_ECS_FAMILY_IPV6) { return smartdns::SERVER_REQUEST_ERROR; } if (memcmp(ecs.addr, "\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00", 16) != 0) { return smartdns::SERVER_REQUEST_ERROR; } if (ecs.source_prefix != 64) { return smartdns::SERVER_REQUEST_ERROR; } smartdns::MockServer::AddIP(request, request->domain.c_str(), "2001:db8::2"); return smartdns::SERVER_REQUEST_OK; } return smartdns::SERVER_REQUEST_SOA; }); server.MockPing(PING_TYPE_ICMP, "1.2.3.4", 60, 100); server.MockPing(PING_TYPE_ICMP, "5.6.7.8", 60, 10); server.MockPing(PING_TYPE_ICMP, "2001:db8::1", 60, 100); server.MockPing(PING_TYPE_ICMP, "2001:db8::2", 60, 10); server.Start(R"""(bind [::]:60053 server 127.0.0.1:62053 -subnet=8.8.8.8/24 -subnet=ffff:ffff:ffff:ffff:ffff::/64 server 127.0.0.1:61053 dualstack-ip-selection no )"""); smartdns::Client client; ASSERT_TRUE(client.Query("a.com A", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "A"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "5.6.7.8"); ASSERT_TRUE(client.Query("a.com AAAA", 60053)); std::cout << client.GetResult() << std::endl; ASSERT_EQ(client.GetAnswerNum(), 1); EXPECT_EQ(client.GetStatus(), "NOERROR"); EXPECT_EQ(client.GetAnswer()[0].GetName(), "a.com"); EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3); EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA"); EXPECT_EQ(client.GetAnswer()[0].GetData(), "2001:db8::2"); } ================================================ FILE: test/client.cc ================================================ /************************************************************************* * * 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 . */ #include "client.h" #include #include #include #include #include #include #include #include namespace smartdns { std::vector StringSplit(const std::string &s, const char delim) { std::vector ret; std::string::size_type lastPos = s.find_first_not_of(delim, 0); std::string::size_type pos = s.find_first_of(delim, lastPos); while (std::string::npos != pos || std::string::npos != lastPos) { ret.push_back(s.substr(lastPos, pos - lastPos)); lastPos = s.find_first_not_of(delim, pos); pos = s.find_first_of(delim, lastPos); } return ret; } DNSRecord::DNSRecord() {} DNSRecord::~DNSRecord() {} bool DNSRecord::Parser(const std::string &line) { std::vector fields_first = StringSplit(line, '\t'); std::vector fields; for (const auto &f : fields_first) { std::vector fields_second = StringSplit(f, ' '); for (const auto &s : fields_second) { if (s.length() > 0) { fields.push_back(s); } } } if (fields.size() < 3) { std::cerr << "Invalid DNS record: " << line << ", size: " << fields.size() << std::endl; return false; } if (fields.size() == 3) { name_ = fields[0]; if (name_.size() > 1) { name_.resize(name_.size() - 1); } class_ = fields[1]; type_ = fields[2]; return true; } name_ = fields[0]; if (name_.size() > 1) { name_.resize(name_.size() - 1); } ttl_ = std::stoi(fields[1]); class_ = fields[2]; type_ = fields[3]; data_ = fields[4]; for (int i = 5; i < fields.size(); i++) { data_ += " " + fields[i]; } return true; } std::string DNSRecord::GetName() { return name_; } std::string DNSRecord::GetType() { return type_; } std::string DNSRecord::GetClass() { return class_; } int DNSRecord::GetTTL() { return ttl_; } std::string DNSRecord::GetData() { return data_; } Client::Client() {} bool Client::Query(const std::string &dig_cmds, int port, const std::string &ip) { Clear(); std::string cmd = "dig "; if (port > 0) { cmd += "-p " + std::to_string(port); } if (ip.length() > 0) { cmd += " @" + ip; } else { cmd += " @127.0.0.1"; } cmd += " " + dig_cmds; cmd += " +tries=1"; FILE *fp = nullptr; fp = popen(cmd.c_str(), "r"); if (fp == nullptr) { return false; } std::shared_ptr pipe(fp, pclose); result_.clear(); char buffer[4096]; usleep(10000); while (fgets(buffer, 4096, pipe.get())) { result_ += buffer; } if (ParserResult() == false) { Clear(); return false; } return true; } std::vector Client::GetQuery() { return records_query_; } std::vector Client::GetAnswer() { return records_answer_; } std::vector Client::GetAuthority() { return records_authority_; } std::vector Client::GetAdditional() { return records_additional_; } std::vector Client::GetOpt() { return records_opt_; } int Client::GetAnswerNum() { return answer_num_; } int Client::GetAuthorityNum() { return authority_num_; } std::string Client::GetStatus() { return status_; } std::string Client::GetServer() { return server_; } int Client::GetQueryTime() { return query_time_; } int Client::GetMsgSize() { return msg_size_; } std::string Client::GetFlags() { return flags_; } std::string Client::GetResult() { return result_; } void Client::Clear() { result_.clear(); answer_num_ = 0; status_.clear(); server_.clear(); query_time_ = 0; msg_size_ = 0; flags_.clear(); records_query_.clear(); records_answer_.clear(); records_authority_.clear(); records_additional_.clear(); } void Client::PrintResult() { std::cout << result_ << std::endl; } bool Client::ParserRecord(const std::string &record_str, std::vector &record) { DNSRecord r; std::vector lines = StringSplit(record_str, '\n'); for (auto &line : lines) { if (r.Parser(line) == false) { return false; } record.push_back(r); } return true; } bool Client::ParserResult() { std::smatch match; std::regex reg_goanswer(";; Got answer:"); if (std::regex_search(result_, match, reg_goanswer) == false) { std::cout << "DIG FAILED:\n" << result_ << std::endl; return false; } std::regex reg_opt(";; OPT PSEUDOSECTION:\\n((?:.|\\n|\\r\\n)+?)\\n;;", std::regex::ECMAScript | std::regex::optimize); if (std::regex_search(result_, match, reg_opt)) { std::string opt_str = match[1]; std::vector lines = StringSplit(opt_str, '\n'); for (auto &line : lines) { if (line.length() <= 0) { continue; } line = line.substr(2); records_opt_.push_back(line); } } std::regex reg_answer_num(", ANSWER: ([0-9]+),"); if (std::regex_search(result_, match, reg_answer_num)) { answer_num_ = std::stoi(match[1]); } std::regex reg_authority_num(", AUTHORITY: ([0-9]+),"); if (std::regex_search(result_, match, reg_authority_num)) { authority_num_ = std::stoi(match[1]); } std::regex reg_status(", status: ([A-Z]+),"); if (std::regex_search(result_, match, reg_status)) { status_ = match[1]; } std::regex reg_server(";; SERVER: ([0-9.]+)#"); if (std::regex_search(result_, match, reg_server)) { server_ = match[1]; } std::regex reg_querytime(";; Query time: ([0-9]+) msec"); if (std::regex_search(result_, match, reg_querytime)) { query_time_ = std::stoi(match[1]); } std::regex reg_msg_size(";; MSG SIZE rcvd: ([0-9]+)"); if (std::regex_search(result_, match, reg_msg_size)) { msg_size_ = std::stoi(match[1]); } std::regex reg_flags(";; flags: ([a-z A-Z]+);"); if (std::regex_search(result_, match, reg_flags)) { flags_ = match[1]; } std::regex reg_question(";; QUESTION SECTION:\\n((?:.|\\n|\\r\\n)+?)\\n{2,}", std::regex::ECMAScript | std::regex::optimize); if (std::regex_search(result_, match, reg_question)) { if (ParserRecord(match[1], records_query_) == false) { return false; } } std::regex reg_answer(";; ANSWER SECTION:\\n((?:.|\\n|\\r\\n)+?)\\n{2,}", std::regex::ECMAScript | std::regex::optimize); if (std::regex_search(result_, match, reg_answer)) { if (ParserRecord(match[1], records_answer_) == false) { return false; } if (answer_num_ != records_answer_.size()) { std::cout << "DIG FAILED: Num Not Match\n" << result_ << std::endl; return false; } } std::regex reg_authority(";; AUTHORITY SECTION:\\n((?:.|\\n|\\r\\n)+?)\\n{2,}", std::regex::ECMAScript | std::regex::optimize); if (std::regex_search(result_, match, reg_authority)) { if (ParserRecord(match[1], records_authority_) == false) { return false; } if (authority_num_ != records_authority_.size()) { std::cout << "DIG FAILED: Num Not Match\n" << result_ << std::endl; return false; } } std::regex reg_addition(";; ADDITIONAL SECTION:\\n((?:.|\\n|\\r\\n)+?)\\n{2,}", std::regex::ECMAScript | std::regex::optimize); if (std::regex_search(result_, match, reg_answer)) { if (ParserRecord(match[1], records_additional_) == false) { return false; } } return true; } Client::~Client() {} } // namespace smartdns ================================================ FILE: test/client.h ================================================ /************************************************************************* * * 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 . */ #ifndef _SMARTDNS_CLIENT_ #define _SMARTDNS_CLIENT_ #include #include #include namespace smartdns { class DNSRecord { public: DNSRecord(); virtual ~DNSRecord(); bool Parser(const std::string &line); std::string GetName(); std::string GetType(); std::string GetClass(); int GetTTL(); std::string GetData(); private: std::string name_; std::string type_; std::string class_; int ttl_; std::string data_; }; class Client { public: Client(); virtual ~Client(); bool Query(const std::string &dig_cmds, int port = 0, const std::string &ip = ""); std::string GetResult(); std::vector GetQuery(); std::vector GetAnswer(); std::vector GetAuthority(); std::vector GetAdditional(); std::vector GetOpt(); int GetAnswerNum(); int GetAuthorityNum(); std::string GetStatus(); std::string GetServer(); int GetQueryTime(); int GetMsgSize(); std::string GetFlags(); void Clear(); void PrintResult(); private: bool ParserResult(); bool ParserRecord(const std::string &record_str, std::vector &record); std::string result_; int answer_num_{0}; int authority_num_{0}; std::string status_; std::string server_; int query_time_{0}; int msg_size_{0}; std::string flags_; std::vector records_query_; std::vector records_answer_; std::vector records_authority_; std::vector records_additional_; std::vector records_opt_; }; } // namespace smartdns #endif // _SMARTDNS_CLIENT_ ================================================ FILE: test/include/utils.h ================================================ /************************************************************************* * * 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 . */ #ifndef _SMARTDNS_TEST_UTILS_ #define _SMARTDNS_TEST_UTILS_ #include #include #include #include namespace smartdns { class DeferGuard { public: template DeferGuard(Callable &&fn) noexcept : fn_(std::forward(fn)) { } DeferGuard(DeferGuard &&other) noexcept { fn_ = std::move(other.fn_); other.fn_ = nullptr; } virtual ~DeferGuard() { if (fn_) { fn_(); } }; DeferGuard(const DeferGuard &) = delete; void operator=(const DeferGuard &) = delete; private: std::function fn_; }; #define SMARTDNS_CONCAT_(a, b) a##b #define SMARTDNS_CONCAT(a, b) SMARTDNS_CONCAT_(a, b) #define Defer ::smartdns::DeferGuard SMARTDNS_CONCAT(__defer__, __LINE__) = [&]() class TempFile { public: TempFile(); TempFile(const std::string &line); virtual ~TempFile(); bool Write(const std::string &line); std::string GetPath(); void SetPattern(const std::string &pattern); void Close(); private: bool NewTempFile(); std::string path_; std::ofstream ofs_; std::string pattern_; }; class Commander { public: Commander(); virtual ~Commander(); bool Run(const std::vector &cmds); bool Run(const std::string &cmd); void Kill(); void Terminate(); int ExitCode(); int GetPid(); private: pid_t pid_{-1}; int exit_code_ = {-1}; }; bool IsCommandExists(const std::string &cmd); std::string GenerateRandomString(int len); int ParserArg(const std::string &cmd, std::vector &args); std::vector GetAvailableIPAddresses(); bool IsICMPAvailable(); } // namespace smartdns #endif // _SMARTDNS_TEST_UTILS_ ================================================ FILE: test/server.cc ================================================ /************************************************************************* * * 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 . */ #include "server.h" #include "smartdns/dns_server.h" #include "smartdns/fast_ping.h" #include "include/utils.h" #include "smartdns/smartdns.h" #include "smartdns/util.h" #include #include #include #include #include #include #include #include #include #include #include #include extern int dns_ping_cap_force_enable; namespace smartdns { MockServer::MockServer() {} MockServer::~MockServer() { Stop(); } bool MockServer::IsRunning() { if (fd_ > 0) { return true; } return false; } void MockServer::Stop() { if (run_ == true) { run_ = false; if (thread_.joinable()) { thread_.join(); } } if (fd_ > 0) { close(fd_); fd_ = -1; } } void MockServer::Run() { while (run_ == true) { struct pollfd fds[1]; fds[0].fd = fd_; fds[0].events = POLLIN; fds[0].revents = 0; int ret = poll(fds, 1, 100); if (ret == 0) { continue; } else if (ret < 0) { sleep(1); continue; } if (fds[0].revents & POLLIN) { struct sockaddr_storage from; socklen_t addrlen = sizeof(from); unsigned char in_buff[4096]; int query_id = 0; int len = recvfrom(fd_, in_buff, sizeof(in_buff), 0, (struct sockaddr *)&from, &addrlen); if (len < 0) { continue; } char packet_buff[4096]; unsigned char response_data_buff[4096]; unsigned char response_packet_buff[4096]; memset(packet_buff, 0, sizeof(packet_buff)); struct dns_packet *packet = (struct dns_packet *)packet_buff; struct ServerRequestContext request; memset(&request, 0, sizeof(request)); ret = dns_decode(packet, sizeof(packet_buff), in_buff, len); if (ret == 0) { request.packet = packet; query_id = packet->head.id; if (packet->head.qr == DNS_QR_QUERY) { struct dns_rrs *rrs = nullptr; int rr_count = 0; int qtype = 0; int qclass = 0; char domain[256]; rrs = dns_get_rrs_start(packet, DNS_RRS_QD, &rr_count); for (int i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { ret = dns_get_domain(rrs, domain, sizeof(domain), &qtype, &qclass); if (ret == 0) { request.domain = domain; request.qtype = (dns_type)qtype; request.qclass = qclass; break; } } } } request.from = (struct sockaddr_storage *)&from; request.fromlen = addrlen; request.request_data = in_buff; request.request_data_len = len; request.response_packet = (struct dns_packet *)response_packet_buff; request.response_data = response_data_buff; request.response_data_len = 0; request.response_data_max_len = sizeof(response_data_buff); struct dns_head head; memset(&head, 0, sizeof(head)); head.id = query_id; head.qr = DNS_QR_ANSWER; head.opcode = DNS_OP_QUERY; head.aa = 0; head.rd = 0; head.ra = 1; head.rcode = DNS_RC_NOERROR; dns_packet_init(request.response_packet, sizeof(response_packet_buff), &head); auto callback_ret = callback_(&request); if (callback_ret == SERVER_REQUEST_ERROR) { dns_packet_init(request.response_packet, sizeof(response_packet_buff), &head); request.response_packet->head.rcode = DNS_RC_SERVFAIL; dns_add_domain(request.response_packet, request.domain.c_str(), request.qtype, request.qclass); request.response_data_len = dns_encode(request.response_data, request.response_data_max_len, request.response_packet); } else if (callback_ret == SERVER_REQUEST_NO_RESPONSE) { continue; } else if (request.response_data_len == 0) { if (callback_ret == SERVER_REQUEST_OK) { request.response_data_len = dns_encode(request.response_data, request.response_data_max_len, request.response_packet); } else if (callback_ret == SERVER_REQUEST_SOA) { struct dns_soa soa; memset(&soa, 0, sizeof(soa)); strncpy(soa.mname, "ns1.example.com", sizeof(soa.mname)); strncpy(soa.rname, "hostmaster.example.com", sizeof(soa.rname)); soa.serial = 1; soa.refresh = 3600; soa.retry = 600; soa.expire = 86400; soa.minimum = 3600; dns_packet_init(request.response_packet, sizeof(response_packet_buff), &head); dns_add_domain(request.response_packet, request.domain.c_str(), request.qtype, request.qclass); request.response_packet->head.rcode = DNS_RC_NXDOMAIN; dns_add_SOA(request.response_packet, DNS_RRS_AN, request.domain.c_str(), 1, &soa); request.response_data_len = dns_encode(request.response_data, request.response_data_max_len, request.response_packet); } } sendto(fd_, request.response_data, request.response_data_len, MSG_NOSIGNAL, (struct sockaddr *)&from, addrlen); } } } bool MockServer::AddIP(struct ServerRequestContext *request, const std::string &domain, const std::string &ip, int ttl) { struct sockaddr_storage addr; socklen_t addrlen = sizeof(addr); memset(&addr, 0, sizeof(addr)); if (GetAddr(ip, "53", SOCK_DGRAM, IPPROTO_UDP, &addr, &addrlen)) { if (addr.ss_family == AF_INET) { struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; dns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), ttl, (unsigned char *)&addr4->sin_addr.s_addr); } else if (addr.ss_family == AF_INET6) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; dns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), ttl, (unsigned char *)&addr6->sin6_addr.s6_addr); } return true; } return false; } bool MockServer::GetAddr(const std::string &host, const std::string port, int type, int protocol, struct sockaddr_storage *addr, socklen_t *addrlen) { struct addrinfo hints; struct addrinfo *result = nullptr; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = type; hints.ai_protocol = protocol; hints.ai_flags = AI_PASSIVE; if (getaddrinfo(host.c_str(), port.c_str(), &hints, &result) != 0) { goto errout; } memcpy(addr, result->ai_addr, result->ai_addrlen); *addrlen = result->ai_addrlen; freeaddrinfo(result); return true; errout: if (result) { freeaddrinfo(result); } return false; } bool MockServer::Start(const std::string &url, ServerRequest callback) { char c_scheme[256]; char c_host[256]; int port; char c_path[256]; int fd; int yes = 1; struct sockaddr_storage addr; socklen_t addrlen; if (callback == nullptr) { return false; } if (parse_uri(url.c_str(), c_scheme, c_host, &port, c_path) != 0) { return false; } std::string scheme(c_scheme); std::string host(c_host); std::string path(c_path); if (scheme != "udp") { return false; } if (GetAddr(host, std::to_string(port), SOCK_DGRAM, IPPROTO_UDP, &addr, &addrlen) == false) { return false; } fd = socket(addr.ss_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) { return false; } setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)); if (bind(fd, (struct sockaddr *)&addr, addrlen) != 0) { close(fd); return false; } run_ = true; thread_ = std::thread(&MockServer::Run, this); fd_ = fd; callback_ = callback; return true; } Server::Server() { mode_ = Server::CREATE_MODE_FORK; } Server::Server(enum Server::CREATE_MODE mode) { mode_ = mode; } void Server::MockPing(PING_TYPE type, const std::string &host, int ttl, float time) { struct MockPingIP ping_ip; ping_ip.type = type; ping_ip.host = host; ping_ip.ttl = ttl; ping_ip.time = time; mock_ping_ips_.push_back(ping_ip); } void Server::StartPost(void *arg) { Server *server = (Server *)arg; bool has_ipv6 = false; for (auto &it : server->mock_ping_ips_) { if (has_ipv6 == false && check_is_ipv6(it.host.c_str()) == 0) { has_ipv6 = true; } fast_ping_fake_ip_add(it.type, it.host.c_str(), it.ttl, it.time); } if (has_ipv6 == true) { fast_ping_fake_ip_add(PING_TYPE_ICMP, "2001::", 64, 10); fast_ping_fake_ip_add(PING_TYPE_TCP, "[2001::]:80", 64, 10); fast_ping_fake_ip_add(PING_TYPE_TCP, "[2001::]:443", 64, 10); dns_server_check_ipv6_ready(); } } bool Server::Start(const std::string &conf, enum CONF_TYPE type) { pid_t pid = 0; int fds[2]; std::string conf_file; fds[0] = 0; fds[1] = 0; Defer { if (fds[0] > 0) { close(fds[0]); } if (fds[1] > 0) { close(fds[1]); } }; const char *default_conf = R"""( log-num 0 log-console yes log-level debug cache-persist no )"""; if (type == CONF_TYPE_STRING) { conf_temp_file_.SetPattern("/tmp/smartdns_conf.XXXXXX"); conf_temp_file_.Write(default_conf); conf_temp_file_.Write(conf); conf_file = conf_temp_file_.GetPath(); } else if (type == CONF_TYPE_FILE) { conf_file = conf; } else { return false; } if (access(conf_file.c_str(), F_OK) != 0) { return false; } conf_file_ = conf_file; if (pipe2(fds, O_CLOEXEC | O_NONBLOCK) != 0) { return false; } if (mode_ == CREATE_MODE_FORK) { pid = fork(); if (pid == 0) { std::vector args = { "smartdns", "-f", "-x", "-c", conf_file, "-p", "-", }; char *argv[args.size() + 1]; for (size_t i = 0; i < args.size(); i++) { argv[i] = (char *)args[i].c_str(); } smartdns_reg_post_func(Server::StartPost, this); dns_ping_cap_force_enable = 1; smartdns_test_main(args.size(), argv, fds[1], 0); _exit(1); } else if (pid < 0) { return false; } } else if (mode_ == CREATE_MODE_THREAD) { thread_ = std::thread([&]() { std::vector args = {"smartdns", "-f", "-x", "-c", conf_file_, "-p", "-", "-S"}; char *argv[args.size() + 1]; for (size_t i = 0; i < args.size(); i++) { argv[i] = (char *)args[i].c_str(); } smartdns_reg_post_func(Server::StartPost, this); dns_ping_cap_force_enable = 1; smartdns_test_main(args.size(), argv, fds[1], 1); smartdns_reg_post_func(nullptr, nullptr); }); } else { return false; } struct pollfd pfd[1]; pfd[0].fd = fds[0]; pfd[0].events = POLLIN; int ret = poll(pfd, 1, 10000); if (ret == 0) { if (thread_.joinable()) { thread_.join(); } if (pid > 0) { kill(pid, SIGKILL); } return false; } pid_ = pid; return pid > 0; } void Server::Stop(bool graceful) { if (thread_.joinable()) { dns_server_stop(); thread_.join(); } if (pid_ > 0) { if (graceful) { kill(pid_, SIGTERM); } else { kill(pid_, SIGKILL); } } if (pid_ > 0) { waitpid(pid_, nullptr, 0); } conf_temp_file_.Close(); pid_ = 0; } bool Server::IsRunning() { if (pid_ <= 0) { return false; } if (waitpid(pid_, nullptr, WNOHANG) == 0) { return true; } return kill(pid_, 0) == 0; } Server::~Server() { Stop(false); } } // namespace smartdns ================================================ FILE: test/server.h ================================================ /************************************************************************* * * 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 . */ #ifndef _SMARTDNS_SERVER_ #define _SMARTDNS_SERVER_ #include "smartdns/dns.h" #include "smartdns/fast_ping.h" #include "include/utils.h" #include #include #include #include #include #include namespace smartdns { class Server { public: struct MockPingIP { PING_TYPE type; std::string host; int ttl; float time; }; enum CONF_TYPE { CONF_TYPE_STRING, CONF_TYPE_FILE, }; enum CREATE_MODE { CREATE_MODE_FORK, CREATE_MODE_THREAD, }; Server(); Server(enum CREATE_MODE mode); virtual ~Server(); void MockPing(PING_TYPE type, const std::string &host, int ttl, float time); bool Start(const std::string &conf, enum CONF_TYPE type = CONF_TYPE_STRING); void Stop(bool graceful = true); bool IsRunning(); private: static void StartPost(void *arg); pid_t pid_; std::thread thread_; int fd_; std::string conf_file_; TempFile conf_temp_file_; std::vector mock_ping_ips_; enum CREATE_MODE mode_; }; struct ServerRequestContext { std::string domain; dns_type qtype; int qclass; struct sockaddr_storage *from; socklen_t fromlen; struct dns_packet *packet; uint8_t *request_data; int request_data_len; uint8_t *response_data; struct dns_packet *response_packet; int response_data_max_len; int response_data_len; }; typedef enum { SERVER_REQUEST_OK = 0, SERVER_REQUEST_ERROR, SERVER_REQUEST_NO_RESPONSE, SERVER_REQUEST_SOA, } ServerRequestResult; using ServerRequest = std::function; class MockServer { public: MockServer(); virtual ~MockServer(); bool Start(const std::string &url, ServerRequest callback); void Stop(); bool IsRunning(); static bool AddIP(struct ServerRequestContext *request, const std::string &domain, const std::string &ip, int ttl = 60); private: void Run(); static bool GetAddr(const std::string &host, const std::string port, int type, int protocol, struct sockaddr_storage *addr, socklen_t *addrlen); int fd_{0}; std::thread thread_; bool run_{false}; ServerRequest callback_{nullptr}; }; } // namespace smartdns #endif // _SMARTDNS_SERVER_ ================================================ FILE: test/test.cc ================================================ /************************************************************************* * * 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 . */ #include "gtest/gtest.h" int main(int argc, char **argv) { if (WEXITSTATUS(system("which dig >/dev/null 2>&1")) != 0) { std::cerr << "dig not found, please install it first." << std::endl; return 1; } ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ================================================ FILE: test/utils.cc ================================================ #include "include/utils.h" #include "smartdns/util.h" #include #include #include #include #include #include #include #include #include #include #include namespace smartdns { TempFile::TempFile() { pattern_ = "/tmp/smartdns-test-tmp.XXXXXX"; } TempFile::TempFile(const std::string &line) { pattern_ = "/tmp/smartdns-test-tmp.XXXXXX"; } TempFile::~TempFile() { if (ofs_.is_open()) { ofs_.close(); ofs_.clear(); } if (path_.length() > 0) { unlink(path_.c_str()); } } void TempFile::Close() { if (ofs_.is_open()) { ofs_.close(); ofs_.clear(); } if (path_.length() > 0) { unlink(path_.c_str()); path_.clear(); } } void TempFile::SetPattern(const std::string &pattern) { pattern_ = pattern; } bool TempFile::Write(const std::string &line) { if (ofs_.is_open() == false) { if (NewTempFile() == false) { return false; } } ofs_.write(line.data(), line.size()); if (ofs_.fail()) { return false; } ofs_.flush(); return true; } bool TempFile::NewTempFile() { char filename[128]; strncpy(filename, "/tmp/smartdns-test-tmp.XXXXXX", sizeof(filename)); int fd = mkstemp(filename); if (fd < 0) { return false; } Defer { close(fd); }; std::ofstream ofs(filename); if (ofs.is_open() == false) { return false; } ofs_ = std::move(ofs); path_ = filename; return true; } std::string TempFile::GetPath() { if (ofs_.is_open() == false) { if (NewTempFile() == false) { return ""; } } return path_; } Commander::Commander() {} Commander::~Commander() { Kill(); } bool Commander::Run(const std::string &cmd) { std::vector args; if (ParserArg(cmd, args) != 0) { return false; } return Run(args); } bool Commander::Run(const std::vector &cmds) { pid_t pid; if (pid_ > 0) { return false; } pid = fork(); if (pid < 0) { return false; } if (pid == 0) { char *argv[cmds.size() + 1]; for (int i = 0; i < cmds.size(); i++) { argv[i] = (char *)cmds[i].c_str(); } argv[cmds.size()] = nullptr; execvp(argv[0], argv); _exit(1); } pid_ = pid; return true; } void Commander::Kill() { if (pid_ <= 0) { return; } kill(pid_, SIGKILL); } void Commander::Terminate() { if (pid_ <= 0) { return; } kill(pid_, SIGTERM); } int Commander::ExitCode() { int wstatus = 0; if (exit_code_ >= 0) { return exit_code_; } if (pid_ <= 0) { return -1; } if (waitpid(pid_, &wstatus, 0) == -1) { return -1; } exit_code_ = WEXITSTATUS(wstatus); return exit_code_; } int Commander::GetPid() { return pid_; } bool IsCommandExists(const std::string &cmd) { char *copy_path = nullptr; char cmd_path[4096]; const char *env_path = getenv("PATH"); char *save_ptr = nullptr; if (env_path == nullptr) { env_path = "/bin:/usr/bin:/usr/local/bin"; } copy_path = strdup(env_path); if (copy_path == nullptr) { return false; } Defer { free(copy_path); }; for (char *tok = strtok_r(copy_path, ":", &save_ptr); tok; tok = strtok_r(nullptr, ":", &save_ptr)) { snprintf(cmd_path, sizeof(cmd_path), "%s/%s", tok, cmd.c_str()); if (access(cmd_path, X_OK) != 0) { continue; } return true; } return false; } std::string GenerateRandomString(int len) { std::string result; static const char alphanum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; result.resize(len); for (int i = 0; i < len; ++i) { result[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; } return result; } int ParserArg(const std::string &cmd, std::vector &args) { std::string arg; char quoteChar = 0; for (char ch : cmd) { if (quoteChar == '\\') { arg.push_back(ch); quoteChar = 0; continue; } if (quoteChar && ch != quoteChar) { arg.push_back(ch); continue; } switch (ch) { case '\'': case '\"': case '\\': quoteChar = quoteChar ? 0 : ch; break; case ' ': case '\t': case '\n': if (!arg.empty()) { args.push_back(arg); arg.clear(); } break; default: arg.push_back(ch); break; } } if (!arg.empty()) { args.push_back(arg); } return 0; } std::vector GetAvailableIPAddresses() { std::vector ipAddresses; struct ifaddrs *ifAddrStruct = nullptr; struct ifaddrs *ifa = nullptr; void *tmpAddrPtr = nullptr; getifaddrs(&ifAddrStruct); for (ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next) { if (!ifa->ifa_addr) { continue; } if (ifa->ifa_addr->sa_family == AF_INET) { // IPv4 address tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; char addressBuffer[INET_ADDRSTRLEN]; inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); std::string ipAddress(addressBuffer); if (!ipAddress.empty() && ipAddress.substr(0, 4) != "127.") { ipAddresses.push_back(ipAddress); } } } if (ifAddrStruct != nullptr) { freeifaddrs(ifAddrStruct); } return ipAddresses; } bool IsICMPAvailable() { int fd = -1; if (has_unprivileged_ping()) { fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); } else { fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); } if (fd < 0) { return false; } close(fd); return true; } } // namespace smartdns