diff --git a/.cursor/rules/smartdns_codebase_guide.mdc b/.cursor/rules/smartdns_codebase_guide.mdc new file mode 100755 index 0000000000..62c1ce11c9 --- /dev/null +++ b/.cursor/rules/smartdns_codebase_guide.mdc @@ -0,0 +1,63 @@ +--- +description: +globs: +alwaysApply: false +--- +# SmartDNS Codebase Guide + +This guide helps in understanding the `smartdns-with-geosite` project structure and key components. + +## Core Configuration + +The primary configuration file for SmartDNS is typically `smartdns.conf`. For detailed information on configuration options and their loading mechanism, refer to the [smartdns_technical analysis_document.md](mdc:smartdns_technical analysis_document.md). + +* **Main Configuration File (Example Path):** `etc/smartdns/smartdns.conf` (Actual path might vary based on installation) +* **Configuration Loading Logic:** Primarily handled within the `src/dns_conf/` directory, with `[src/dns_conf/dns_conf.c](mdc:src/dns_conf/dns_conf.c)` being a key file for parsing and `_config_item[]` definitions. + +## Main Source Code Directory: `src/` + +The core logic of SmartDNS resides in the `[src/](mdc:src)` directory. + +* **Entry Point:** The program execution likely starts in `[src/main.c](mdc:src/main.c)` or `[src/smartdns.c](mdc:src/smartdns.c)`. +* **Core DNS Logic:** `[src/dns.c](mdc:src/dns.c)` +* **Cache Implementation:** `[src/dns_cache.c](mdc:src/dns_cache.c)` +* **Plugin System:** `[src/dns_plugin.c](mdc:src/dns_plugin.c)` + +### Key Subdirectories in `src/`: + +* **`[src/dns_conf/](mdc:src/dns_conf)`**: Handles parsing and management of configuration files. + * `[src/dns_conf/dns_conf.c](mdc:src/dns_conf/dns_conf.c)`: Central configuration parsing. + * `[src/dns_conf/domain_rule.c](mdc:src/dns_conf/domain_rule.c)`: Domain-specific rule processing. + * `[src/dns_conf/set_file.c](mdc:src/dns_conf/set_file.c)`: Handles `domain-set` and geosite files. +* **`[src/dns_server/](mdc:src/dns_server)`**: Contains the server-side logic for handling DNS requests. + * `[src/dns_server/dns_server.c](mdc:src/dns_server/dns_server.c)`: Core server operations, listening, request handling. + * `[src/dns_server/rules.c](mdc:src/dns_server/rules.c)`: Server-side rule application. + * `[src/dns_server/cache.c](mdc:src/dns_server/cache.c)` (if distinct from top-level `dns_cache.c`, or `server_cache.c` as mentioned in docs): Server-specific cache interactions. +* **`[src/dns_client/](mdc:src/dns_client)`**: Implements the client-side logic for querying upstream DNS servers. + * `[src/dns_client/dns_client.c](mdc:src/dns_client/dns_client.c)`: Core client operations. + * Files for specific protocols like `client_udp.c`, `client_tcp.c`, `client_tls.c`, `client_https.c`, `client_quic.c`. +* **`[src/utils/](mdc:src/utils)`**: Contains utility functions (networking, logging, string manipulation, SSL). + * `[src/utils/log.c](mdc:src/utils/log.c)` (or `tlog.c` at `src/`): Logging. + * `[src/utils/ssl.c](mdc:src/utils/ssl.c)`: SSL/TLS utilities. +* **`[src/lib/](mdc:src/lib)`**: Internal libraries and data structures (e.g., `radix.c`, `rbtree.c`, `art.c`). +* **`[src/fast_ping/](mdc:src/fast_ping)`**: Implements ICMP and other ping mechanisms for speed testing. + * `[src/fast_ping/fast_ping.c](mdc:src/fast_ping/fast_ping.c)`: Main logic for fast ping. +* **`[src/http_parse/](mdc:src/http_parse)`**: Logic for parsing HTTP, relevant for DoH. +* **`[src/include/](mdc:src/include)`**: Header files for the project, especially `[src/include/smartdns/](mdc:src/include/smartdns)`. + +## Technical Documentation + +For a comprehensive understanding of SmartDNS's architecture, configuration options, and internal workings, refer to: + +* [smartdns_technical analysis_document.md](mdc:smartdns_technical analysis_document.md) + +This document provides detailed explanations of: +* Configuration directives. +* Core service flow. +* Cache mechanisms. +* Speed testing. +* Rule matching (including domain-set and geosite). +* Encrypted DNS protocol handling (DoT, DoH, DoQ). +* Dualstack IP selection. + +This rule should help in quickly navigating to relevant parts of the codebase based on the technical documentation and our discussion. diff --git a/Makefile b/Makefile index bcc6ed98da..20560432cb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/ReadMe.md b/ReadMe.md index 78db0afa61..c539015dbd 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,161 +1,46 @@ -# SmartDNS - **[English](ReadMe_en.md)** -![SmartDNS](doc/smartdns-banner.png) -SmartDNS 是一个运行在本地的 DNS 服务器,它接受来自本地客户端的 DNS 查询请求,然后从多个上游 DNS 服务器获取 DNS 查询结果,并将访问速度最快的结果返回给客户端,以此提高网络访问速度。 -SmartDNS 同时支持指定特定域名 IP 地址,并高性匹配,可达到过滤广告的效果; 支持DOT(DNS over TLS)和DOH(DNS over HTTPS),更好的保护隐私。 - -与 DNSmasq 的 all-servers 不同,SmartDNS 返回的是访问速度最快的解析结果。 - -支持树莓派、OpenWrt、华硕路由器原生固件和 Windows 系统等。 - -## 使用指导 - -SmartDNS官网:[https://pymumu.github.io/smartdns](https://pymumu.github.io/smartdns) - -## 软件效果展示 +在SmartDNS基础上,domain-set增加geosite域名匹配规则文本文件的支持,以便实现域名服务器分组。 -### 仪表盘 +##域名规则支持以下几种匹配方式: +1) 以 domain: 开头,域匹配。e.g: domain:google.com 会匹配自身 google.com,以及其子域名 www.google.com, maps.l.google.com 等。 +2) 以 full: 开头,完整匹配。e.g: full:google.com 只会匹配自身。 +3) 以 keyword: 开头,关键字匹配。e.g: keyword:google.com 会匹配包含这个字段的域名,如 google.com.hk, www.google.com.hk。 +4) 以 regexp: 开头,正则匹配。e.g: "regexp:\.goo.*\.com$" 匹配 "www.google.com" 或 "fonts.googleapis.com",但不匹配 "google.com"。 -![SmartDNS-WebUI](doc/smartdns-webui.png) +如果没有指定匹配方式,默认为域匹配。 -### 速度对比 +匹配方式按如下顺序生效: full、domain优先, regexp、keyword次之,regexp 和 keyword 规则生效顺序为规则导入的顺序。 -**阿里 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 +##编译cre2: ``` - -**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 +apt-get install libssl-dev libre2-dev automake libtool texinfo +git clone https://github.com/marcomaggi/cre2.git +cd cre2 +./autogen.sh +touch ./doc/version.texi +mkdir build && cd build +../configure +make +make install +ldconfig ``` +##编译v2dat: +``` +git clone https://github.com/urlesistiana/v2dat.git +cd v2dat +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o v2dat -trimpath -ldflags "-s -w -buildid=" . +``` +##数据下载及转换: +``` +wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat +./v2dat unpack geosite -f google geosite.dat +``` +##配置smartdns.conf: +``` +domain-set -name geosite_google -type geosite -file /etc/smartdns/geosite_google.txt +nameserver /domain-set:geosite_google/google +server 8.8.8.8 -group google -exclude-default-group +``` +SmartDNS官网:[https://pymumu.github.io/smartdns](https://pymumu.github.io/smartdns) -从对比看出,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 查询及服务,以及非 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 协议开源。 diff --git a/ReadMe_en.md b/ReadMe_en.md index d69203f47c..47c4b4e51f 100644 --- a/ReadMe_en.md +++ b/ReadMe_en.md @@ -1,151 +1,47 @@ -# SmartDNS +Based on SmartDNS, the domain-set adds support for geosite domain matching rule text files to enable grouping of domain servers. -![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 (DNS over TLS), DoH (DNS over HTTPS), better protect privacy, -Avoiding DNS pollution and improving network access speed, supports high-performance ad filtering. +###Domain rules support the following matching methods: -Unlike dnsmasq's all-servers, smartdns returns the fastest access resolution. +Starting with domain: for domain matching. e.g., domain:google.com will match google.com itself and its subdomains like www.google.com, maps.l.google.com, etc. -Support Raspberry Pi, openwrt, ASUS router, Windows and other devices. +Starting with full: for exact matching. e.g., full:google.com will only match itself. -## Usage +Starting with keyword: for keyword matching. e.g., keyword:google.com will match domains containing this field, such as google.com.hk, www.google.com.hk. -Please visit website: [https://pymumu.github.io/smartdns](https://pymumu.github.io/smartdns/en) +Starting with regexp: for regular expression matching. e.g., "regexp:\.goo.*\.com$" matches www.google.com or fonts.googleapis.com but not google.com. -## Software Show +If no matching method is specified, domain matching is used by default. -### Dashboard +The matching methods take effect in the following order: full and domain have priority, followed by regexp and keyword. The order of effectiveness for regexp and keyword rules depends on their import sequence. -![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 +###Compile cre2: ``` - -**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 - +apt-get install libssl-dev libre2-dev automake libtool texinfo +git clone https://github.com/marcomaggi/cre2.git +cd cre2 +./autogen.sh +touch ./doc/version.texi +mkdir build && cd build +../configure +make +make install +ldconfig ``` - -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) 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. +###Compile v2dat: +``` +git clone https://github.com/urlesistiana/v2dat.git +cd v2dat +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o v2dat -trimpath -ldflags "-s -w -buildid=" +``` +###Data download and conversion: +``` +wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat +./v2dat unpack geosite -f google geosite.dat +``` +###Configure smartdns.conf: +``` +domain-set -name geosite_google -type geosite -file /etc/smartdns/geosite_google.txt +nameserver /domain-set:geosite_google/google +server 8.8.8.8 -group google -exclude-default-group +``` +SmartDNS official website: https://pymumu.github.io/smartdns \ No newline at end of file diff --git a/etc/init.d/smartdns b/etc/init.d/smartdns index 67cda8f920..ab3f4dde44 100644 --- a/etc/init.d/smartdns +++ b/etc/init.d/smartdns @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/etc/smartdns/smartdns.conf b/etc/smartdns/smartdns.conf index f40aa204b1..76f0e6ed10 100644 --- a/etc/smartdns/smartdns.conf +++ b/etc/smartdns/smartdns.conf @@ -133,7 +133,7 @@ bind [::]:53 # 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 +# force-qtype-SOA 65 # Enable IPV4, IPV6 dual stack IP optimization selection strategy # dualstack-ip-selection-threshold [num] (0~1000) @@ -392,7 +392,7 @@ log-level info # 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 +# [-t] -type [list|geosite|geositelist]: domain set type, list/geosite/geositelist # [-f] -file [path/to/set]: file path of domain set # # example: @@ -435,11 +435,14 @@ log-level info # load plugin # plugin [path/to/file] [args] -# plugin /usr/lib/libsmartdns-ui.so --p 8080 -i 0.0.0.0 -r /usr/share/smartdns/wwwroot + +# web ui plugin +# plugin /usr/lib/libsmartdns-ui.so # smartdns-ui.www-root /usr/share/smartdns/wwwroot # smartdns-ui.ip http://0.0.0.0:6080 +# smartdns-ui.ip https://0.0.0.0:6080 # smartdns-ui.token-expire 600 -# smartdns-ui.token-secret 123456 +# smartdns-ui.max-query-log-age 86400 # smartdns-ui.enable-terminal yes # smartdns-ui.enable-cors yes # smartdns-ui.user admin diff --git a/package/build-pkg.sh b/package/build-pkg.sh index bf32584316..b838e5da69 100755 --- a/package/build-pkg.sh +++ b/package/build-pkg.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (C) 2018-2024 Nick Peng (pymumu@gmail.com) +# Copyright (C) 2018-2025 Nick Peng (pymumu@gmail.com) CURR_DIR=$(cd $(dirname $0);pwd) VER="`date +"1.%Y.%m.%d-%H%M"`" diff --git a/package/debian/DEBIAN/copyright b/package/debian/DEBIAN/copyright index f622e54376..28dfa75062 100644 --- a/package/debian/DEBIAN/copyright +++ b/package/debian/DEBIAN/copyright @@ -3,5 +3,5 @@ Upstream-Name: smartdns Source: http://github.com/pymumu/smartdns Files: * -Copyright: 2018-2024 Nick peng +Copyright: 2018-2025 Nick peng License: proprietary diff --git a/package/debian/make.sh b/package/debian/make.sh index 8591681ae2..160babea2a 100755 --- a/package/debian/make.sh +++ b/package/debian/make.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/entware/package/net/smartdns/Makefile b/package/entware/package/net/smartdns/Makefile new file mode 100644 index 0000000000..2433884aea --- /dev/null +++ b/package/entware/package/net/smartdns/Makefile @@ -0,0 +1,58 @@ +# +# Copyright (c) 2018-2020 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:=3.2023.41.9 +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/weiping/smartdns.git +PKG_SOURCE_VERSION:=73f8a643bab39c2d3f36fad83f90e7d37576010b +PKG_MIRROR_HASH:=73f8a643bab39c2d3f36fad83f90e7d37576010b + +PKG_MAINTAINER:=Nick Peng +PKG_LICENSE:=GPL-3.0-or-later +PKG_LICENSE_FILES:=LICENSE + +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk + +MAKE_VARS += VER=$(PKG_VERSION) +MAKE_PATH:=src + +define Package/smartdns + SECTION:=net + CATEGORY:=Network + TITLE:=smartdns server + DEPENDS:=+libpthread +libopenssl +cre2 + URL:=https://www.github.com/pymumu/smartdns/ +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. +endef + +define Package/smartdns/conffiles +/opt/etc/smartdns/smartdns.conf +endef + +define Package/smartdns/install + $(INSTALL_DIR) $(1)/opt/sbin $(1)/opt/etc/init.d $(1)/opt/etc/smartdns + $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/smartdns $(1)/opt/sbin/smartdns + $(INSTALL_BIN) ./files/S38smartdns $(1)/opt/etc/init.d +# $(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 + $(INSTALL_CONF) $(PKG_BUILD_DIR)/etc/smartdns/smartdns.conf $(1)/opt/etc/smartdns +endef + +$(eval $(call BuildPackage,smartdns)) diff --git a/package/entware/package/net/smartdns/files/S38smartdns b/package/entware/package/net/smartdns/files/S38smartdns new file mode 100644 index 0000000000..612b5eced1 --- /dev/null +++ b/package/entware/package/net/smartdns/files/S38smartdns @@ -0,0 +1,10 @@ +#!/bin/sh + +ENABLED=yes +PROCS=smartdns +ARGS="" +PREARGS="" +DESC=$PROCS +PATH=/opt/sbin:/opt/bin:/opt/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +. /opt/etc/init.d/rc.func diff --git a/package/entware/package/net/smartdns/patches/500-fix_path.patch b/package/entware/package/net/smartdns/patches/500-fix_path.patch new file mode 100644 index 0000000000..fe0ff3fc81 --- /dev/null +++ b/package/entware/package/net/smartdns/patches/500-fix_path.patch @@ -0,0 +1,28 @@ +--- a/src/smartdns.c ++++ b/src/smartdns.c +@@ -47,7 +47,7 @@ + #define RESOLVE_FILE "/etc/resolv.conf" + #define MAX_LINE_LEN 1024 + #define MAX_KEY_LEN 64 +-#define SMARTDNS_PID_FILE "/var/run/smartdns.pid" ++#define SMARTDNS_PID_FILE "/opt/var/run/smartdns.pid" + #define TMP_BUFF_LEN_32 32 + + static int verbose_screen; +--- a/src/dns_conf.h ++++ b/src/dns_conf.h +@@ -48,8 +48,8 @@ extern "C" { +#define DEFAULT_DNS_TLS_PORT 853 +#define DEFAULT_DNS_HTTPS_PORT 443 +#define DNS_MAX_CONF_CNAME_LEN 256 +-#define SMARTDNS_CONF_FILE "/etc/smartdns/smartdns.conf" +-#define SMARTDNS_LOG_FILE "/var/log/smartdns/smartdns.log" +-#define SMARTDNS_AUDIT_FILE "/var/log/smartdns/smartdns-audit.log" +-#define SMARTDNS_CACHE_FILE "/tmp/smartdns.cache" +-#define SMARTDNS_DEBUG_DIR "/tmp/smartdns" ++#define SMARTDNS_CONF_FILE "/opt/etc/smartdns/smartdns.conf" ++#define SMARTDNS_LOG_FILE "/opt/var/log/smartdns.log" ++#define SMARTDNS_AUDIT_FILE "/opt/var/log/smartdns-audit.log" ++#define SMARTDNS_CACHE_FILE "/opt/tmp/smartdns.cache" ++#define SMARTDNS_DEBUG_DIR "/opt/tmp/smartdns" + diff --git a/package/entware/package/net/smartdns/patches/501-fix_conf.patch b/package/entware/package/net/smartdns/patches/501-fix_conf.patch new file mode 100644 index 0000000000..f3081953e1 --- /dev/null +++ b/package/entware/package/net/smartdns/patches/501-fix_conf.patch @@ -0,0 +1,20 @@ +--- a/etc/smartdns/smartdns.conf 2023-03-14 10:59:57.606454800 +0800 ++++ b/etc/smartdns/smartdns.conf 2023-03-14 11:17:43.566189638 +0800 +@@ -147,7 +147,7 @@ + # log-num: number of logs, 0 means disable log + log-level info + +-# log-file /var/log/smartdns/smartdns.log ++# log-file /opt/var/log/smartdns/smartdns.log + # log-size 128k + # log-num 2 + # log-file-mode [mode]: file mode of log file. +@@ -157,7 +157,7 @@ + # 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-audit.log ++# audit-file /opt/var/log/smartdns-audit.log + # audit-console [yes|no]: output audit log to console. + # audit-file-mode [mode]: file mode of audit file. + # audit-size 128k \ No newline at end of file diff --git a/package/linux/install b/package/linux/install index e4b2794c07..c9d2fa8b33 100644 --- a/package/linux/install +++ b/package/linux/install @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci-compat/control/postinst b/package/luci-compat/control/postinst index 6c67a24ee5..3d926f4f9a 100644 --- a/package/luci-compat/control/postinst +++ b/package/luci-compat/control/postinst @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci-compat/control/prerm b/package/luci-compat/control/prerm index 3fe4c3d424..88989e9a60 100644 --- a/package/luci-compat/control/prerm +++ b/package/luci-compat/control/prerm @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci-compat/files/etc/uci-defaults/50_luci-smartdns b/package/luci-compat/files/etc/uci-defaults/50_luci-smartdns index 8c9580c1b7..df27bec961 100644 --- a/package/luci-compat/files/etc/uci-defaults/50_luci-smartdns +++ b/package/luci-compat/files/etc/uci-defaults/50_luci-smartdns @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci-compat/files/luci/controller/smartdns.lua b/package/luci-compat/files/luci/controller/smartdns.lua index 616fdb9c0d..0bab0cd866 100644 --- a/package/luci-compat/files/luci/controller/smartdns.lua +++ b/package/luci-compat/files/luci/controller/smartdns.lua @@ -1,5 +1,5 @@ -- --- Copyright (C) 2018-2024 Ruilin Peng (Nick) . +-- Copyright (C) 2018-2025 Ruilin Peng (Nick) . -- -- smartdns is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by diff --git a/package/luci-compat/files/luci/model/cbi/smartdns/smartdns.lua b/package/luci-compat/files/luci/model/cbi/smartdns/smartdns.lua index ed8b40e5e3..d55df3ee64 100644 --- a/package/luci-compat/files/luci/model/cbi/smartdns/smartdns.lua +++ b/package/luci-compat/files/luci/model/cbi/smartdns/smartdns.lua @@ -1,5 +1,5 @@ -- --- Copyright (C) 2018-2024 Ruilin Peng (Nick) . +-- Copyright (C) 2018-2025 Ruilin Peng (Nick) . -- -- smartdns is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by diff --git a/package/luci-compat/files/luci/model/cbi/smartdns/upstream.lua b/package/luci-compat/files/luci/model/cbi/smartdns/upstream.lua index 152215ffb2..11b465b777 100644 --- a/package/luci-compat/files/luci/model/cbi/smartdns/upstream.lua +++ b/package/luci-compat/files/luci/model/cbi/smartdns/upstream.lua @@ -1,5 +1,5 @@ -- --- Copyright (C) 2018-2024 Ruilin Peng (Nick) . +-- Copyright (C) 2018-2025 Ruilin Peng (Nick) . -- -- smartdns is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by diff --git a/package/luci-compat/files/luci/model/smartdns.lua b/package/luci-compat/files/luci/model/smartdns.lua index 777b775c43..fde7a8b5d3 100644 --- a/package/luci-compat/files/luci/model/smartdns.lua +++ b/package/luci-compat/files/luci/model/smartdns.lua @@ -1,5 +1,5 @@ -- --- Copyright (C) 2018-2024 Ruilin Peng (Nick) . +-- Copyright (C) 2018-2025 Ruilin Peng (Nick) . -- -- smartdns is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by diff --git a/package/luci-compat/make.sh b/package/luci-compat/make.sh index fd5dc83810..e1f19b0add 100755 --- a/package/luci-compat/make.sh +++ b/package/luci-compat/make.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci-lite/control/postinst b/package/luci-lite/control/postinst index e185a124d3..ed018d9831 100644 --- a/package/luci-lite/control/postinst +++ b/package/luci-lite/control/postinst @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci-lite/control/prerm b/package/luci-lite/control/prerm index 8b17439c2a..7add01f16b 100644 --- a/package/luci-lite/control/prerm +++ b/package/luci-lite/control/prerm @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci-lite/files/root/etc/init.d/smartdns-lite b/package/luci-lite/files/root/etc/init.d/smartdns-lite index b081097064..fbf59acc69 100644 --- a/package/luci-lite/files/root/etc/init.d/smartdns-lite +++ b/package/luci-lite/files/root/etc/init.d/smartdns-lite @@ -1,6 +1,6 @@ #!/bin/sh /etc/rc.common # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci-lite/files/root/www/luci-static/resources/view/smartdns-lite/smartdns-lite.js b/package/luci-lite/files/root/www/luci-static/resources/view/smartdns-lite/smartdns-lite.js index 06e3b33ff9..83c3257ca8 100644 --- a/package/luci-lite/files/root/www/luci-static/resources/view/smartdns-lite/smartdns-lite.js +++ b/package/luci-lite/files/root/www/luci-static/resources/view/smartdns-lite/smartdns-lite.js @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/package/luci-lite/make.sh b/package/luci-lite/make.sh index 6e16c80371..9634a60995 100755 --- a/package/luci-lite/make.sh +++ b/package/luci-lite/make.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci/control/postinst b/package/luci/control/postinst index 6c67a24ee5..3d926f4f9a 100644 --- a/package/luci/control/postinst +++ b/package/luci/control/postinst @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci/control/prerm b/package/luci/control/prerm index 3fe4c3d424..88989e9a60 100644 --- a/package/luci/control/prerm +++ b/package/luci/control/prerm @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/luci/files/root/www/luci-static/resources/view/smartdns/smartdns.js b/package/luci/files/root/www/luci-static/resources/view/smartdns/smartdns.js index 7957964a19..6c64f05d28 100644 --- a/package/luci/files/root/www/luci-static/resources/view/smartdns/smartdns.js +++ b/package/luci/files/root/www/luci-static/resources/view/smartdns/smartdns.js @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/package/luci/make.sh b/package/luci/make.sh index 53162a0bb7..0ee23e1189 100755 --- a/package/luci/make.sh +++ b/package/luci/make.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/openwrt/Makefile b/package/openwrt/Makefile index ae11418b69..bbda3a8bcf 100644 --- a/package/openwrt/Makefile +++ b/package/openwrt/Makefile @@ -1,5 +1,5 @@ # -# Copyright (c) 2018-2024 Nick Peng (pymumu@gmail.com) +# Copyright (c) 2018-2025 Nick Peng (pymumu@gmail.com) # This is free software, licensed under the GNU General Public License v3. # diff --git a/package/openwrt/control/postinst b/package/openwrt/control/postinst index 5ab47d4da7..4748cebac9 100644 --- a/package/openwrt/control/postinst +++ b/package/openwrt/control/postinst @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/openwrt/control/prerm b/package/openwrt/control/prerm index efc494cb0d..750504bea5 100644 --- a/package/openwrt/control/prerm +++ b/package/openwrt/control/prerm @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/openwrt/files/etc/init.d/smartdns b/package/openwrt/files/etc/init.d/smartdns index 6507bdd549..e8a57074f7 100644 --- a/package/openwrt/files/etc/init.d/smartdns +++ b/package/openwrt/files/etc/init.d/smartdns @@ -1,6 +1,6 @@ #!/bin/sh /etc/rc.common # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -87,7 +87,7 @@ stop_forward_dnsmasq() set_main_dns() { local hostip - hostip="$(uci -q get network.lan.ipaddr)" + 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" @@ -861,7 +861,7 @@ load_service() [ "$ui" = "1" ] && { conf_append "plugin" "/usr/lib/libsmartdns_ui.so" conf_append "smartdns-ui.www-root" "/usr/share/smartdns/wwwroot" - conf_append "data-dir" "/etc/smartdns/data" + conf_append "data-dir" "/var/lib/smartdns" } { diff --git a/package/openwrt/make.sh b/package/openwrt/make.sh index 9e8decb549..5a8aa7b8e3 100755 --- a/package/openwrt/make.sh +++ b/package/openwrt/make.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/optware/S50smartdns b/package/optware/S50smartdns index b6bd02dda6..30ae9d5feb 100644 --- a/package/optware/S50smartdns +++ b/package/optware/S50smartdns @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/optware/control/postinst b/package/optware/control/postinst index 6e80db7a01..6b0e5242ee 100644 --- a/package/optware/control/postinst +++ b/package/optware/control/postinst @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/optware/control/prerm b/package/optware/control/prerm index 9f9bbe388d..d16f2e0ffc 100644 --- a/package/optware/control/prerm +++ b/package/optware/control/prerm @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/package/optware/make.sh b/package/optware/make.sh index bc8ddcef27..daa525a725 100755 --- a/package/optware/make.sh +++ b/package/optware/make.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/plugin/demo/Makefile b/plugin/demo/Makefile index 087b7577eb..5603d58985 100644 --- a/plugin/demo/Makefile +++ b/plugin/demo/Makefile @@ -1,5 +1,5 @@ -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/plugin/demo/demo.c b/plugin/demo/demo.c index 81cb8b307f..97ecb40808 100644 --- a/plugin/demo/demo.c +++ b/plugin/demo/demo.c @@ -1,11 +1,11 @@ #include "demo.h" -#include "dns_server.h" -#include "util.h" +#include "smartdns/dns_server.h" +#include "smartdns/tlog.h" +#include "smartdns/util.h" #include #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, diff --git a/plugin/demo/demo.h b/plugin/demo/demo.h index 4f3389ba86..78b19e98ca 100644 --- a/plugin/demo/demo.h +++ b/plugin/demo/demo.h @@ -2,7 +2,7 @@ #ifndef SMART_DNS_PLUGIN_DEMO_H #define SMART_DNS_PLUGIN_DEMO_H -#include "dns_plugin.h" +#include "smartdns/dns_plugin.h" #ifdef __cplusplus extern "C" { diff --git a/plugin/smartdns-ui/Makefile b/plugin/smartdns-ui/Makefile index 7b7de47c1b..a6e47011c2 100644 --- a/plugin/smartdns-ui/Makefile +++ b/plugin/smartdns-ui/Makefile @@ -1,5 +1,5 @@ -# Copyright (C) 2018-2023 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/build.rs b/plugin/smartdns-ui/build.rs index 7a655f9e20..417193d644 100644 --- a/plugin/smartdns-ui/build.rs +++ b/plugin/smartdns-ui/build.rs @@ -32,6 +32,7 @@ fn get_git_commit_version() { 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") @@ -51,7 +52,7 @@ fn link_smartdns_lib() { let ignored_macros = IgnoreMacros(vec!["IPPORT_RESERVED".into()].into_iter().collect()); let mut bindings_builder = - bindgen::Builder::default().header(format!("{}/smartdns.h", smartdns_src_dir)); + 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)); } diff --git a/plugin/smartdns-ui/src/data_server.rs b/plugin/smartdns-ui/src/data_server.rs index 5c7abb3c49..34e3c5d57c 100644 --- a/plugin/smartdns-ui/src/data_server.rs +++ b/plugin/smartdns-ui/src/data_server.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -384,8 +384,11 @@ impl DataServer { self.db.delete_client_by_id(id) } - pub fn get_client_list(&self) -> Result, Box> { - self.db.get_client_list() + pub fn get_client_list( + &self, + param: &ClientListGetParam, + ) -> Result> { + self.db.get_client_list(Some(param)) } pub fn get_top_client_top_list( @@ -617,7 +620,7 @@ impl DataServer { 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 batch_mode = *this.recv_in_batch.lock().unwrap(); { let mut _rx = this.notify_rx.lock().unwrap(); diff --git a/plugin/smartdns-ui/src/data_stats.rs b/plugin/smartdns-ui/src/data_stats.rs index f36cd50bd1..552e8ab2d1 100644 --- a/plugin/smartdns-ui/src/data_stats.rs +++ b/plugin/smartdns-ui/src/data_stats.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,9 @@ */ use std::{ - collections::HashMap, error::Error, sync::{ - atomic::{AtomicU32, AtomicU64}, - RwLock, - } + collections::HashMap, + error::Error, + sync::{atomic::AtomicU32, RwLock}, }; use crate::{data_server::DataServerConfig, db::*, dns_log, smartdns::*, utils}; @@ -29,10 +28,14 @@ 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, @@ -41,15 +44,35 @@ struct DataStatsItem { request_dropped: AtomicU64, } +#[cfg(not(target_has_atomic = "64"))] +struct DataStatsItem { + total_request: Arc>, + total_blocked_request: Arc>, + qps: AtomicU32, + qps_count: AtomicU32, + request_dropped: Arc>, +} + impl DataStatsItem { pub fn new() -> Self { - DataStatsItem { + #[cfg(target_has_atomic = "64")] + let ret = DataStatsItem { total_request: 0.into(), total_blocked_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)), + qps: 0.into(), + qps_count: 0.into(), + request_dropped: Arc::new(Mutex::new(0)), + }; + + return ret; } pub fn get_qps(&self) -> u32 { @@ -66,24 +89,69 @@ impl DataStatsItem { } pub fn add_request_drop(&self, count: u64) { - self.request_dropped.fetch_and(count, Ordering::Relaxed); + #[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_total_request(&self) -> u64 { - return self.total_request.load(Ordering::Relaxed); + #[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) { - self.total_request.fetch_add(total, Ordering::Relaxed); + #[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 { - return self.total_blocked_request.load(Ordering::Relaxed); + #[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) { - self.total_blocked_request - .fetch_add(total, Ordering::Relaxed); + #[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; + } } #[allow(dead_code)] @@ -271,20 +339,12 @@ impl DataStats { 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 - ); + 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 - ); + dns_log!(LogLevel::WARN, "refresh domain top list error: {}", e); } let _ = self .db diff --git a/plugin/smartdns-ui/src/data_upstream_server.rs b/plugin/smartdns-ui/src/data_upstream_server.rs index 94dfb47e1f..45be94c5a9 100644 --- a/plugin/smartdns-ui/src/data_upstream_server.rs +++ b/plugin/smartdns-ui/src/data_upstream_server.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/src/db.rs b/plugin/smartdns-ui/src/db.rs index ea8a617b76..329060510d 100644 --- a/plugin/smartdns-ui/src/db.rs +++ b/plugin/smartdns-ui/src/db.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -114,6 +114,51 @@ pub struct DomainListGetParamCursor { 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, @@ -241,6 +286,11 @@ impl DB { [], )?; + 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, @@ -1333,7 +1383,7 @@ impl DB { 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 ( @@ -1361,16 +1411,240 @@ impl DB { Ok(()) } - pub fn get_client_list(&self) -> Result, Box> { + 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") { + is_desc_order = is_desc_order; + } 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 mut ret = Vec::new(); - let mut stmt = conn.prepare("SELECT id, client_ip, mac, hostname, last_query_timestamp FROM client").unwrap(); - let rows = stmt.query_map([], |row| { + + 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)?, @@ -1380,14 +1654,37 @@ impl DB { }) }); + 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); + 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) } @@ -1398,7 +1695,7 @@ impl DB { } let conn = conn.as_ref().unwrap(); - + let ret = conn.execute("DELETE FROM client WHERE id = ?", &[&id]); if let Err(e) = ret { diff --git a/plugin/smartdns-ui/src/http_api_msg.rs b/plugin/smartdns-ui/src/http_api_msg.rs index d0f3a6bd2c..5d4a69f3ce 100644 --- a/plugin/smartdns-ui/src/http_api_msg.rs +++ b/plugin/smartdns-ui/src/http_api_msg.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -294,22 +294,31 @@ pub fn api_msg_parse_client_list(data: &str) -> Result, Box, total_count: u32) -> String { +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.len(), + "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 + client_list_result.client_list .iter() .map(|x| { - let s = json!({ - "id": x.id, - "client_ip": x.client_ip, - "mac": x.mac, - "hostname": x.hostname, - "last_query_timestamp": x.last_query_timestamp, - }); - s + api_msg_gen_json_object_client(x) }) .collect::>() @@ -796,7 +805,6 @@ pub fn api_msg_parse_stats_overview(data: &str) -> Result String json_str.to_string() } -pub fn api_msg_parse_hourly_query_count( - data: &str, -) -> Result> { +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() { diff --git a/plugin/smartdns-ui/src/http_error.rs b/plugin/smartdns-ui/src/http_error.rs index a309027642..dc590d7ab7 100644 --- a/plugin/smartdns-ui/src/http_error.rs +++ b/plugin/smartdns-ui/src/http_error.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/src/http_jwt.rs b/plugin/smartdns-ui/src/http_jwt.rs index c3a199868b..28398c1acd 100644 --- a/plugin/smartdns-ui/src/http_jwt.rs +++ b/plugin/smartdns-ui/src/http_jwt.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/src/http_server.rs b/plugin/smartdns-ui/src/http_server.rs index 1f13f150ef..a73bca3c3f 100644 --- a/plugin/smartdns-ui/src/http_server.rs +++ b/plugin/smartdns-ui/src/http_server.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/src/http_server_api.rs b/plugin/smartdns-ui/src/http_server_api.rs index 96b5da151b..15bc0aa212 100644 --- a/plugin/smartdns-ui/src/http_server_api.rs +++ b/plugin/smartdns-ui/src/http_server_api.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -640,7 +640,11 @@ impl API { if cursor.is_some() || total_count.is_some() { let param_cursor = DomainListGetParamCursor { id: if cursor.is_some() { cursor } else { None }, - total_count: total_count.unwrap(), + total_count: if total_count.is_some() { + total_count.unwrap() + } else { + 0 + }, direction: cursor_direction, }; param.cursor = Some(param_cursor); @@ -717,11 +721,65 @@ impl API { async fn api_client_get_list( this: Arc, _param: APIRouteParam, - _req: Request, + 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(); + let ret = data_server + .get_client_list(¶m) + .map_err(|e| e.to_string()); if let Err(e) = ret { return Err(e.to_string()); } @@ -729,21 +787,27 @@ impl API { let ret = ret.unwrap(); return Ok(ret); - }).await; + }) + .await; - let ret = match ret { - Ok(v) => v, - Err(e) => { - return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str()); - }, - }; - + 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 body = api_msg_gen_client_list(&client_list, client_list.len() as u32); + 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) } @@ -846,7 +910,7 @@ impl API { } if key.is_some() { - let key : String = key.unwrap(); + 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"); diff --git a/plugin/smartdns-ui/src/http_server_stream.rs b/plugin/smartdns-ui/src/http_server_stream.rs index f6e26c71db..e869e0357c 100644 --- a/plugin/smartdns-ui/src/http_server_stream.rs +++ b/plugin/smartdns-ui/src/http_server_stream.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,7 @@ 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::*; @@ -41,6 +42,23 @@ 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, @@ -51,6 +69,9 @@ pub async fn serve_log_stream( 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() => { @@ -99,6 +120,33 @@ pub async fn serve_log_stream( 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; } diff --git a/plugin/smartdns-ui/src/lib.rs b/plugin/smartdns-ui/src/lib.rs index 7de1385d73..b77d1bbaca 100644 --- a/plugin/smartdns-ui/src/lib.rs +++ b/plugin/smartdns-ui/src/lib.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/src/plugin.rs b/plugin/smartdns-ui/src/plugin.rs index 0b112a8501..b3327fc5eb 100644 --- a/plugin/smartdns-ui/src/plugin.rs +++ b/plugin/smartdns-ui/src/plugin.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -95,7 +95,7 @@ impl SmartdnsPlugin { let www_root = Plugin::dns_conf_plugin_config("smartdns-ui.www-root"); if let Some(www_root) = www_root { - http_conf.http_root = www_root; + http_conf.http_root = smartdns_conf_get_conf_fullpath(&www_root); } let ip = Plugin::dns_conf_plugin_config("smartdns-ui.ip"); @@ -112,7 +112,11 @@ impl SmartdnsPlugin { } dns_log!(LogLevel::INFO, "www root: {}", http_conf.http_root); - if let Some(token_expire) = matches.opt_str("token-expire") { + 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!( diff --git a/plugin/smartdns-ui/src/server_log.rs b/plugin/smartdns-ui/src/server_log.rs index 7c6af3c459..a90f87596a 100644 --- a/plugin/smartdns-ui/src/server_log.rs +++ b/plugin/smartdns-ui/src/server_log.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/src/smartdns.rs b/plugin/smartdns-ui/src/smartdns.rs index b5f185b1f4..15e29027e9 100644 --- a/plugin/smartdns-ui/src/smartdns.rs +++ b/plugin/smartdns-ui/src/smartdns.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -266,6 +266,23 @@ pub fn smartdns_enable_update_neighbour(enable: bool) { } } +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(); diff --git a/plugin/smartdns-ui/src/utils.rs b/plugin/smartdns-ui/src/utils.rs index 80adc48127..f0f1ed019c 100644 --- a/plugin/smartdns-ui/src/utils.rs +++ b/plugin/smartdns-ui/src/utils.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/src/whois.rs b/plugin/smartdns-ui/src/whois.rs index c08cae1f1d..f4ca89c277 100644 --- a/plugin/smartdns-ui/src/whois.rs +++ b/plugin/smartdns-ui/src/whois.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/tests/common/client.rs b/plugin/smartdns-ui/tests/common/client.rs index 107a1a98a7..69f768be16 100644 --- a/plugin/smartdns-ui/tests/common/client.rs +++ b/plugin/smartdns-ui/tests/common/client.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/tests/common/mod.rs b/plugin/smartdns-ui/tests/common/mod.rs index e36c1f761a..73e7067aee 100644 --- a/plugin/smartdns-ui/tests/common/mod.rs +++ b/plugin/smartdns-ui/tests/common/mod.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/tests/common/server.rs b/plugin/smartdns-ui/tests/common/server.rs index 64f4a75bc8..de4eede61d 100644 --- a/plugin/smartdns-ui/tests/common/server.rs +++ b/plugin/smartdns-ui/tests/common/server.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/tests/httpserver_test.rs b/plugin/smartdns-ui/tests/httpserver_test.rs index 2fe22d05fa..37c1d8204b 100644 --- a/plugin/smartdns-ui/tests/httpserver_test.rs +++ b/plugin/smartdns-ui/tests/httpserver_test.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/plugin/smartdns-ui/tests/restapi_test.rs b/plugin/smartdns-ui/tests/restapi_test.rs index 1b1792fadc..e7172397bd 100644 --- a/plugin/smartdns-ui/tests/restapi_test.rs +++ b/plugin/smartdns-ui/tests/restapi_test.rs @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -493,7 +493,7 @@ fn test_rest_api_get_client() { let res = client.login("admin", "password"); assert!(res.is_ok()); - let c = client.get("/api/client"); + let c = client.get("/api/client?page_size=4096"); assert!(c.is_ok()); let (code, body) = c.unwrap(); assert_eq!(code, 200); diff --git a/smartdns_geosite_domain_rules_analysis_optimization.md b/smartdns_geosite_domain_rules_analysis_optimization.md new file mode 100755 index 0000000000..1c90e3ddaf --- /dev/null +++ b/smartdns_geosite_domain_rules_analysis_optimization.md @@ -0,0 +1,172 @@ + +## SmartDNS `domain-set` Geosite/Geositelist 功能及域名匹配机制分析与优化建议 + +### 1. 引言 + +本文档旨在深入分析 `smartdns-with-geosite` (SmartDNS 的一个 fork 版本) 中 `domain-set` 的 `geosite` 和 `geositelist` 类型的实现方式,以及它们如何与 `nameserver` 指令协同工作以实现高级域名解析策略。同时,本文也将探讨针对此域名匹配过程的一些潜在优化建议。 + +SmartDNS 是一个本地 DNS 服务器,通过从多个上游服务器查询并将最快的结果返回给客户端来提升网络访问速度。此 fork 版本增强了 `domain-set` 功能,使其能够更好地处理来自文本文件的域名列表(如 geosite 数据),以实现灵活的域名服务器分组和流量控制。 + +### 2. `domain-set` 配置与功能 + +#### 2.1 `geosite` 和 `geositelist` 类型 + +在 `smartdns.conf` 文件中,可以如下配置 `geosite` 或 `geositelist` 类型的 `domain-set`: + +``` +domain-set -name -type [geosite|geositelist] -file /path/to/domain_rules.txt +``` + +* `-name `: 域名集合的唯一名称。 +* `-type [geosite|geositelist]`: 指定类型。 +* `-file /path/to/domain_rules.txt`: 包含域名规则的文本文件路径。 + +#### 2.2 规则文件格式与前缀 + +规则文件为纯文本,每行一条规则,支持以下前缀: + +1. `domain:`: **域匹配** (如 `domain:google.com` 匹配 `google.com` 及其所有子域)。 +2. `full:`: **完整匹配** (如 `full:example.com` 仅匹配 `example.com`)。 +3. `keyword:`: **关键字匹配** (仅 `geosite` 支持,如 `keyword:google` 匹配含 "google" 的域名,内部转为正则 `^.*google.*$`)。 +4. `regexp:`: **正则表达式匹配** (仅 `geosite` 支持)。 +5. **无前缀**: 默认为**域匹配**。 + +文件中以 `#` 开头的行为注释。 + +#### 2.3 `geosite` 与 `geositelist` 的区别 + +* **`geosite`**: 支持所有四种匹配前缀,功能更全面。 +* **`geositelist`**: **仅支持** `domain` 和 `full` 匹配(及默认域匹配),不支持 `keyword` 和 `regexp`。设计上可能为了在处理大规模精确域名列表时有更高性能,避免了正则开销。 + +#### 2.4 规则匹配优先级 + +1. `full:` +2. `domain:` +3. `regexp:` (按文件导入顺序) +4. `keyword:` (按文件导入顺序) + +(注:`regexp` 和 `keyword` 优先级及顺序主要受其后续在 `domain-rules` 或 `nameserver` 中如何被引用的影响,以及 `dns_regexp_match` 内部的匹配逻辑。) + +### 3. `nameserver` 与 `domain-set` 的协同 + +`nameserver` 指令可将匹配特定 `domain-set` 的域名查询路由到指定的上游服务器组。 + +```smartdnsconf +domain-set -name geosite_google -type geosite -file /etc/smartdns/geosite_google.txt +server 8.8.8.8 -group google_dns_group +nameserver /domain-set:geosite_google/google_dns_group +``` + +当一个域名查询(如 `mail.google.com`)进来时,如果它匹配了 `geosite_google` 集合中的规则 (例如 `domain:google.com`),则该查询会被转发到 `google_dns_group`。 + +### 4. 深入理解:规则加载实现细节 + +规则的加载始于 `smartdns.conf` 的解析,并涉及到从指定文件读取和处理每一条域名规则。 + +#### 4.1. `domain-set` 指令解析 + +* **配置文件解析**: 当 SmartDNS 启动或重载配置时,它会解析 `smartdns.conf` 文件。 +* **`_config_domain_set` 函数 (位于 `src/dns_conf/domain_set.c`)**: 此函数专门负责处理 `domain-set` 配置行。 + * 它使用 `getopt_long_only` 来解析命令行参数式的选项,如 `-name`, `-type`, 和 `-file`。 + * 解析到的信息被用来填充一个 `struct dns_domain_set_name` 结构体实例,该实例包含了 `type` (如 `DNS_DOMAIN_SET_GEOSITE`, `DNS_DOMAIN_SET_GEOSITELIST`) 和 `file` (文件路径)。 + * 每个 `struct dns_domain_set_name` 实例会根据其 `-name` 参数通过哈希计算(`hash_string`)被添加到一个全局的哈希表 `dns_domain_set_name_table` 中。这个哈希表允许多个规则文件与同一个 `domain-set` 名称关联(形成一个列表 `set_name_list`),尽管通常一个 `domain-set` 定义对应一个文件。 + +#### 4.2. 从文件加载规则 + +* **触发加载**: 当配置文件中的其他指令(如 `nameserver /domain-set:my_rules/...` 或 `domain-rules /domain-set:my_rules/...`)引用一个已定义的 `domain-set` 时,SmartDNS 会查找该 `domain-set` 的元数据(类型和文件路径)。 +* **`_config_domain_rule_set_each` 函数 (位于 `src/dns_conf/domain_rule.c`)**: 此函数会遍历指定 `domain-set` 名称下的所有条目(通常是一个)。根据条目的类型 (`set_name_item->type`),它会调用相应的加载函数。 +* **`_config_domain_rule_each_from_geosite` 函数 (位于 `src/dns_conf/set_file.c`)**: 对于 `DNS_DOMAIN_SET_GEOSITE` 和 `DNS_DOMAIN_SET_GEOSITELIST` 类型,此函数被调用来处理规则文件。 + * **文件打开与逐行读取**: 使用 `fopen` 打开 `-file` 指定的文件,并通过 `fgets` 逐行读取内容。 + * **注释与空行处理**: 以 `#` 开头的行或内容为空(或仅含换行符)的行会被忽略。 + * **前缀解析与域名提取**: + * 使用 `sscanf(line, "%255s", domain)` 初步提取行内容。 + * 通过 `strncmp` 检查是否存在如 `full:`, `domain:`, `keyword:`, `regexp:` 等前缀。 + * 如果存在前缀,则从原始字符串中移除前缀部分,得到纯粹的域名或模式。例如,对于 `full:example.com`,提取出 `example.com`。 + * **`geositelist` 类型的特殊逻辑**: 如果当前 `domain-set` 的类型是 `DNS_DOMAIN_SET_GEOSITELIST`,并且行中的规则是 `keyword:` 或 `regexp:` 类型,则该行规则会被直接跳过,不进行加载。 + * **`keyword:` 规则转换**: 如果是 `keyword:somekey`,提取 `somekey` 后,会将其构造成一个正则表达式,如 `sprintf(domain,"^.*%s.*$", buf)`,其中 `buf` 存储的是 `somekey`。然后调用 `dns_regexp_insert(domain)` 将此新生成的正则表达式添加到全局的正则规则列表中。 + * **`regexp:` 规则处理**: 如果是 `regexp:pattern`,提取 `pattern` 后,直接调用 `dns_regexp_insert(domain)` 将用户定义的正则表达式添加到全局正则规则列表中。 + * **无前缀规则**: 如果没有上述特定前缀,该行内容被视为一个域名,默认按 `domain:` 规则处理。 + * **回调机制传递规则**: + * 对于每一条成功解析的域名/模式(经过前缀处理和 `keyword` 转换后),会调用一个传入的 `callback` 函数。 + * 在 `_config_domain_rule_set_each` 调用 `_config_domain_rule_each_from_geosite` 时,传入的 `callback` 通常是 `_config_domain_rule_add_callback`。 + * `_config_domain_rule_add_callback` 内部会调用 `_config_domain_rule_add`。此函数是规则添加的核心,它接收处理后的域名/模式字符串、规则类型(如 `DOMAIN_RULE_NAMESERVER`, `DOMAIN_RULE_ADDRESS_IPV4` 等,这里主要是指后续应用到这些域名的动作类型)以及具体的规则数据。 + * `_config_domain_rule_add` 负责将这些域名/模式(经过反转等键处理)及其关联的规则动作存入到 ART (Adaptive Radix Tree) 数据结构中。对于原始是 `keyword` 或 `regexp` 的规则,虽然它们本身被 `dns_regexp_insert` 处理,但它们所代表的模式字符串(如 `^.*somekey.*$`)也会作为键被 `_config_domain_rule_add` 添加到 ART 中,以便后续 `nameserver` 或 `domain-rules` 指令可以引用这些模式并绑定具体的动作。 + +通过这一系列步骤,`domain-set` 文件中定义的规则被逐条解析、处理(包括类型转换和特殊类型跳过),并最终通过回调函数整合进 SmartDNS 核心的规则存储和匹配系统中。 + +### 5. 深入理解:域名匹配实现细节 + +#### 5.1 域名规则的存储 + +* **核心数据结构:ART (Adaptive Radix Tree)** + * 对于 `full:` 和 `domain:` 规则,域名(以及从 `keyword/regexp` 匹配后得到的模式字符串)在存入 ART 前会经过**反转**处理。例如,`www.google.com` 变为 `.com.google.www.`。这种处理使得后续可以通过前缀查找实现高效的后缀匹配。 + * ART (`domain_rule.tree`) 被用于存储这些反转后的域名键。每个 SmartDNS 配置组 (`dns_conf_group`) 维护自己的 ART。 +* **规则节点 (`struct dns_domain_rule`)** + * ART 中的每个相关节点会关联一个 `struct dns_domain_rule`。 + * 此结构包含一个 `rules[DOMAIN_RULE_MAX]` 数组,用于存储该域名模式对应的各种具体规则(如 `nameserver`、`address` 等)。 + * 还包含 `sub_rule_only` 和 `root_rule_only` 标志,用于更精确地控制规则的应用范围(仅子域、仅根域或两者)。 +* **正则表达式的存储** + * `keyword:` 和 `regexp:` 规则在 `_config_domain_rule_each_from_geosite` 中通过 `dns_regexp_insert()` 进行处理,它们被编译并存储在一个独立的列表或结构中,由 `dns_regexp.c` (未在当前分析中详述,但从函数名推断) 相关逻辑管理。 + +#### 5.2 域名匹配查询过程 + +匹配过程主要由 `src/dns_server/rules.c` 中的 `_dns_server_get_domain_rule_by_domain_ext` 函数驱动: + +1. **域名预处理**: + * 待查询的域名同样进行反转和添加 `.` 前后缀(如 `www.google.com` -> `.com.google.www.`)。 +2. **阶段一:ART 精确及后缀匹配**: + * 使用 `art_substring_walk` 在 ART 中查找所有匹配反转后域名的前缀。这相当于查找原始域名的所有有效后缀及其精确匹配。 + * 例如,对于 `.com.google.www.`,会查找 `.`, `.com.`, `.com.google.`, `.com.google.www.`。 + * `_dns_server_get_rules` 回调函数被触发,收集与这些 ART 节点关联的 `struct dns_domain_rule` 中的具体规则,并考虑 `sub_rule_only` / `root_rule_only` 标志。 + * 由于 `art_substring_walk` 的特性和回调处理,通常更长(更精确)的后缀匹配规则会自然覆盖较短的,实现了“最长后缀匹配优先”。 +3. **阶段二:正则表达式匹配 (若 ART 未直接命中)**: + * 如果第一阶段 ART 查找未直接找到规则 (`!walk_args.match`) 并且系统中存在已加载的正则表达式 (`has_regexp()`),则进入此阶段。 + * 调用 `dns_regexp_match(domain, matched_pattern_buffer)`,将原始查询域名与存储的正则表达式列表逐一比较。 + * **关键点**: 如果匹配成功,`dns_regexp_match` 会返回实际匹配上的那个**正则表达式字符串本身**。然后,这个**字符串**(经过反转等处理后)将再次作为键,在 ART 中进行查找 (`art_substring_walk`),以获取与此正则表达式模式关联的具体规则(如 `nameserver` 地址)。 + * 这意味着,一个 `keyword:` 或 `regexp:` 规则若要实际生效(例如指定一个特定的上游服务器),不仅需要在 `domain-set` 文件中定义,还需要在 `smartdns.conf` 的其他地方(如 `domain-rules` 或 `nameserver` 指令)明确引用这个正则表达式字符串(或其所属的 `domain-set`),从而将一个具体的操作(action)与这个模式绑定。 +4. **阶段三:规则整合与最终化**: + * 所有收集到的规则会经过 `_dns_server_update_rule_by_flags` 处理,根据 `DOMAIN_RULE_FLAGS` 中的忽略标志(如 `DOMAIN_FLAG_NAMESERVER_IGNORE`)进行调整。 + * 最终,`request->domain_rule` 结构中包含了所有适用于当前查询域名的、经过优先级和标志调整后的有效规则集合。 + +#### 5.3 优先级实现机制总结 + +* **`full:` vs `domain:`**: ART 的“最长前缀匹配”(由于域名反转,实为“最长后缀匹配”)特性自然保证了更精确的规则优先。 +* **ART 规则 (full/domain) vs 正则表达式规则 (keyword/regexp)**: 代码逻辑显示,先进行 ART 查找。如果 ART 查找成功 (`walk_args.match` 为真),则可能跳过后续代价较高的正则表达式匹配步骤。这符合 `full/domain` 规则优先于 `regexp/keyword` 的设计。 +* **`regexp:` vs `keyword:`**: 两者都作为正则表达式处理。它们之间的优先级取决于 `dns_regexp_match` 内部的匹配顺序(通常是它们被插入的顺序)。 + +### 6. 域名匹配优化建议 + +基于上述分析,以下是一些潜在的优化方向: + +1. **正则表达式预编译与共享**: + * **建议**: 确保所有从 `geosite` 文件加载的 `keyword:` 和 `regexp:` 规则在 SmartDNS 启动或配置重载时仅编译一次。 + * **理由**: 正则表达式编译是耗时操作。如果多个 `domain-set` 或规则文件使用了完全相同的正则表达式字符串,它们应该共享同一个已编译的正则对象实例,以减少内存占用和重复编译开销。 + +2. **优化正则表达式匹配顺序**: + * **建议**: 如果存在大量正则表达式规则,考虑根据其统计匹配频率或特异性(匹配范围大小)来排序匹配。将匹配频率高或特异性强(能更快确定匹配或不匹配)的规则放在前面。 + * **理由**: `dns_regexp_match` 目前可能是按插入顺序遍历。优化顺序可以减少平均匹配时间。但这需要额外的统计或启发式逻辑。 + +3. **审视正则匹配后的二次 ART 查找**: + * **现状**: 当前 `keyword/regexp` 匹配成功后,使用匹配到的正则表达式字符串作为键再次查询 ART 以获取具体动作。 + * **建议**: 评估是否可以将已编译的正则表达式对象直接与动作(如 `struct dns_domain_rule` 的指针或其子规则)关联起来,例如通过一个独立的哈希表(键为编译后的正则对象,值为动作指针)或在正则对象结构中直接包含动作。 + * **理由**: 这可能减少一次 ART 查找,简化逻辑。但需注意,当前设计可能允许同一个正则表达式字符串在配置文件中被不同规则(如不同的 `nameserver` 指向不同group)复用其“键”的角色。优化时需保持这种灵活性或评估其必要性。 + +4. **使用更快的正则表达式引擎 (如果适用)**: + * **现状**: 项目 `ReadMe.md` 提及编译 `cre2`,它是 Google RE2 的 C 接口。RE2 以其线性的最坏情况时间复杂度(避免灾难性回溯)而闻名,通常是一个很好的选择。 + * **建议**: 确认 `cre2` 确实是当前实现中用于 `dns_regexp_match` 的引擎。如果未来考虑替换,需仔细评估性能和兼容性。 + +5. **针对超大规模规则集的内存和缓存优化 (高级)**: + * **建议**: 对于包含数十万甚至数百万条 `full/domain` 规则的极端情况,可以研究 ART 的内存压缩技术或针对特定访问模式优化其节点布局以改善 CPU 缓存命中率。 + * **理由**: 虽然 ART 本身效率较高,但在极端规模下,内存和缓存仍可能成为瓶颈。这通常需要非常底层的优化。 + +6. **引入布隆过滤器 (Bloom Filter) 进行正则初筛 (高级)**: + * **建议**: 在迭代匹配大量正则表达式之前,可以使用布隆过滤器对查询域名进行一次快速检查。如果布隆过滤器判定域名不可能匹配任何一个正则表达式,则可以跳过整个正则匹配列表。 + * **理由**: 可以显著减少对不匹配域名的无效正则比较次数。但布隆过滤器有误报率,且需要额外维护。 + +7. **性能分析 (Profiling)**: + * **建议**: 针对性地使用性能分析工具(如 `perf`)对 SmartDNS 在加载和处理大规模、复杂 `domain-set` 规则时的行为进行分析。 + * **理由**: 这是找出实际性能瓶颈(ART 操作、特定正则表达式、内存分配等)的最直接方法,从而指导优化方向。 + +### 7. 结论 + +SmartDNS 的 `smartdns-with-geosite` fork 通过引入 `geosite` 和 `geositelist` 类型的 `domain-set`,并结合 ART 和反向域名存储等技术,提供了一套强大且相对高效的域名规则管理和匹配机制。理解其内部实现有助于更好地利用其功能,并为进一步的性能优化提供了基础。上述优化建议提供了一些可能的改进方向,但具体实施需要结合实际场景和性能分析结果进行权衡。 diff --git a/smartdns_technical analysis_document.md b/smartdns_technical analysis_document.md new file mode 100755 index 0000000000..ddff46396f --- /dev/null +++ b/smartdns_technical analysis_document.md @@ -0,0 +1,1966 @@ + +## SmartDNS 技术分析文档 + +### 1. 引言 + +SmartDNS 是一个高性能的本地 DNS 服务器,其核心目标是为用户提供最快的域名解析结果,从而优化互联网访问体验。它通过从多个上游 DNS 服务器并发查询,并结合灵活的测速机制和规则引擎,智能地选择最佳 IP 地址返回给客户端。 + +**核心特性包括:** + +* **并发与优选**:同时向上游服务器查询,并根据配置的测速模式(如 PING、TCP 探测)和响应模式(如最快 IP、首个 PING 通 IP)返回最优结果。 +* **多协议支持**:支持传统的 UDP/TCP DNS,以及加密的 DoT (DNS over TLS), DoH (DNS over HTTPS), DoQ (DNS over QUIC) 和 DoH3 (DNS over HTTP/3) 协议,增强了查询的私密性和安全性。 +* **高级路由与规则引擎**: + * 基于域名、域名组 (`domain-set`)、Geosite 规则、客户端 IP/MAC 实现精细化的 DNS 路由。 + * 可为特定域名指定静态 IP (`address`)、CNAME、SRV、HTTPS 记录。 + * 可将特定域名解析结果送入 ipset 或 nftables set,方便与防火墙联动。 +* **强大的缓存机制**:内置高效缓存,支持持久化、预取 (`prefetch-domain`) 和过期服务 (`serve-expired`),减少延迟。 +* **灵活的配置**:通过 `smartdns.conf` 文件提供丰富的配置选项,满足各种复杂场景需求。 +* **跨平台**:支持 Linux (包括 OpenWrt、树莓派等嵌入式设备)、Windows (通过 WSL) 等多种平台。 +* **轻量与高效**:采用多线程异步 I/O 模型,资源占用少。 +* **插件扩展**:支持通过插件扩展功能,例如 `smartdns-ui` 提供了 Web 管理界面。 + +本技术文档旨在深入分析 SmartDNS 的配置文件、配置加载机制、核心服务流程以及关键特性的实现原理,帮助开发者和高级用户更好地理解和使用 SmartDNS。 + +### 2. 配置文件 (`smartdns.conf`) 详解 + +SmartDNS 的所有行为都通过其主配置文件 `smartdns.conf` (通常位于 `/etc/smartdns/smartdns.conf` 或编译时指定的路径) 来控制。该文件采用纯文本格式,以 `#` 开头的行为注释行。配置项通常由指令和参数构成。 + +#### 2.1 基本服务配置 + +这类配置定义了 SmartDNS 服务自身的基础运行参数。 + +* **`server-name `** + * **描述**:设置 DNS 服务器的名称,主要用于某些 DNS 协议(如 TXT 记录查询服务器版本)或日志中识别。 + * **参数**:`` - 自定义的服务器名称。 + * **默认值**:系统主机名。 + * **示例**:`server-name my-smartdns-server` + +* **`resolv-hostname [yes|no]`** + * **描述**:是否解析本机的完整主机名 (hostname) 到其 IP 地址。如果设置为 `yes`,SmartDNS 会尝试将本机的主机名解析为一个 A/AAAA 记录。 + * **默认值**:未明确配置则可能为 `no` 或取决于系统。 + * **示例**:`resolv-hostname yes` + +* **`user `** + * **描述**:指定 SmartDNS 服务运行的用户身份。出于安全考虑,建议在初始化 (如绑定特权端口) 后切换到非 root 用户运行。 + * **参数**:`` - 系统中存在的用户名。 + * **默认值**:启动 SmartDNS 的用户,通常是 root。 + * **示例**:`user nobody` + +* **`conf-file [-group ]`** + * **描述**:包含另一个配置文件。这允许用户将复杂的配置拆分成多个小文件,方便管理。支持使用 `glob` 通配符 (如 `conf-file *.conf`)。 + * **参数**: + * ``: 要包含的配置文件的路径。 + * `-group ` (可选): 如果指定,则被包含文件中的规则仅应用于名为 `` 的服务器组的上下文。这允许为不同的服务器组定义独立的规则集。 + * **示例**: + ``` + conf-file /etc/smartdns/custom/blacklist.conf + conf-file /etc/smartdns/ads-rules.conf -group adblock-group + conf-file /etc/smartdns/domains.d/*.conf + ``` + +* **`bind <[IP]:[port][@device]> [options...]`** +* **`bind-tcp <[IP]:[port][@device]> [options...]`** +* **`bind-tls <[IP]:[port][@device]> [options...]`** +* **`bind-https <[IP]:[port][@device]> [options...]`** + * **描述**:指定 SmartDNS 监听 DNS 请求的本地 IP 地址和端口。可以配置多个 `bind` 指令以监听不同的地址、端口或协议。 + * `bind`: 监听 UDP DNS 请求。 + * `bind-tcp`: 监听 TCP DNS 请求。 + * `bind-tls`: 监听 DoT (DNS over TLS) 请求。需要配合 `bind-cert-key-file` 和 `bind-cert-file`。 + * `bind-https`: 监听 DoH (DNS over HTTPS) 请求。需要配合 `bind-cert-key-file` 和 `bind-cert-file`。 + * **参数**: + * `<[IP]:[port]>`:监听的 IP 地址和端口。 + * `IP` 可以是 IPv4 或 IPv6 地址。`:` 表示监听所有可用 IPv4 地址,`[::]` 表示监听所有可用 IPv6 地址。 + * `port` 是监听的端口号,默认为 53 (对于 UDP/TCP) 或 853 (TLS) 或 443 (HTTPS)。 + * `@device` (可选): 将监听绑定到特定的网络接口 (如 `@eth0`)。 + * **`options` (可为每个 `bind` 单独设置,覆盖全局或组设置)**: + * `-group `: 将此监听端口收到的请求默认归属到指定的服务器规则组 ``。客户端规则可以覆盖此设置。 + * `-no-rule-addr`: 对于此监听端口收到的请求,跳过 `address` 规则的匹配。 + * `-no-rule-nameserver`: 跳过 `nameserver` 规则的匹配。 + * `-no-rule-ipset` / `-no-rule-nftset`: 跳过 `ipset` / `nftset` 规则的应用。 + * `-no-speed-check`: 禁用对解析结果的测速。 + * `-no-cache`: 禁用 DNS 缓存查找和存储。 + * `-no-rule-soa`: 跳过返回 SOA 的 `address /domain/#` 规则。 + * `-no-dualstack-selection`: 禁用双栈 IP 优选逻辑。 + * `-no-ip-alias`: 忽略 IP 别名规则。 + * `-force-aaaa-soa`: 强制 AAAA 查询返回 SOA (如果无 AAAA 记录)。 + * `-force-https-soa`: 强制 HTTPS (SVCB) 查询返回 SOA (如果无 HTTPS 记录)。 + * `-no-serve-expired`: 不提供过期的缓存记录。 + * `-no-rules`: 跳过所有域名规则(`address`, `nameserver`, `ipset`, `nftset`, `domain-rules`等)。 + * `-ipset `: 将通过此监听端口成功解析的 IP 地址(默认全部,或指定 IPv4/IPv6)添加到指定的 ipset 集合中。这会覆盖全局或域名特定的 ipset 设置。 + * `-nftset <#4:fam#tbl#set4,#6:fam#tbl#set6>`: 类似 `-ipset`,但用于 nftables set。 + * **TLS/HTTPS 特定参数 (全局或在 `bind-tls`/`bind-https` 块内)**: + * `bind-cert-key-file `: TLS 私钥文件路径 (PEM 格式)。 + * `bind-cert-file `: TLS 证书文件路径 (PEM 格式,通常包含完整证书链)。 + * `bind-cert-key-pass `: TLS 私钥的密码 (如果已加密)。 + * **示例**: + ``` + bind :53 + bind [::]:53 + bind 192.168.1.1:5353 -group trusted_clients -no-speed-check + bind-tcp :53 + bind-tls :853 + bind-cert-file /etc/smartdns/certs/server.crt + bind-cert-key-file /etc/smartdns/certs/server.key + ``` + +* **`tcp-idle-time `** + * **描述**:设置 TCP 连接(包括 DoT, DoH)的最大空闲超时时间。如果一个 TCP 连接在此时间内没有任何活动,将被关闭。 + * **参数**:`` - 超时时间,单位为秒。 + * **默认值**:通常为 60 或 120 秒。 + * **示例**:`tcp-idle-time 90` + +#### 2.2 缓存配置 + +控制 SmartDNS 的查询结果缓存行为。 + +* **`cache-size `** + * **描述**:设置 DNS 缓存中可以存储的最大条目数量。 + * **参数**: + * ``: 缓存条目数。 + * `0`: 禁用缓存。 + * `-1`: 自动根据内存调整 (具体逻辑依赖实现)。 + * **默认值**:例如 `32768`。 + * **示例**:`cache-size 65536` + +* **`cache-mem-size [k|m|g]`** + * **描述**:设置 DNS 缓存可以使用的最大物理内存量。当达到此限制时,可能会触发旧缓存条目的淘汰。 + * **参数**:`` 后可跟 `k` (KB), `m` (MB), `g` (GB)。 + * **默认值**:无明确限制或根据 `cache-size` 估算。 + * **示例**:`cache-mem-size 128m` + +* **`cache-persist [yes|no]`** + * **描述**:是否在 SmartDNS 重启时持久化缓存内容。如果为 `yes`,缓存会在退出时保存到文件,并在启动时加载。 + * **默认值**:`no`。 + * **示例**:`cache-persist yes` + +* **`cache-file `** + * **描述**:当 `cache-persist yes` 时,指定缓存持久化文件的路径。 + * **参数**:``。 + * **默认值**:例如 `/tmp/smartdns.cache` 或 `/var/cache/smartdns/smartdns.cache`。 + * **示例**:`cache-file /opt/smartdns/cache/smartdns.cache` + +* **`cache-checkpoint-time `** + * **描述**:当 `cache-persist yes` 时,SmartDNS 会定期将内存中的缓存数据写入到持久化文件。此配置项设置写入操作的时间间隔。 + * **参数**:`` - 时间间隔,单位为秒。 + * **默认值**:例如 `86400` (一天)。 + * **示例**:`cache-checkpoint-time 3600` (一小时) + +* **`prefetch-domain [yes|no]`** + * **描述**:是否启用域名预取功能。当缓存中的某个域名记录即将过期或被频繁访问时,SmartDNS 会主动向上游服务器查询该域名以更新缓存,保持其新鲜度,从而减少下次查询该域名时的延迟。 + * **默认值**:`yes`。 + * **示例**:`prefetch-domain yes` + +* **`serve-expired [yes|no]`** + * **描述**:当 DNS 查询命中了一条已过期的缓存记录时,是否仍然使用这条过期数据来响应客户端。如果设置为 `yes`,可以减少在某些网络环境下等待上游响应的时间,但可能会返回不再准确的 IP。通常会配合预取功能在后台更新这条过期记录。 + * **默认值**:`yes`。 + * **示例**:`serve-expired yes` + +* **`serve-expired-ttl `** + * **描述**:当 `serve-expired yes` 且 SmartDNS 决定使用一条过期缓存响应时,此配置项指定了该过期响应中 IP 地址的 TTL 值。这个 TTL 通常较短,以促使客户端尽快重新查询。如果设置为 `0`,则可能使用原始过期记录的 TTL 或一个非常小的值。 + * **默认值**:`0`。 + * **示例**:`serve-expired-ttl 60` + +* **`serve-expired-reply-ttl `** + * **描述**:(此项与 `serve-expired-ttl` 功能可能重叠或为其别名,具体行为需参照实现细节)。通常指当返回过期数据时,在DNS响应中设置的TTL值。 + * **默认值**:`30`。 + * **示例**:`serve-expired-reply-ttl 30` + +#### 2.3 IP 地址过滤与别名 + +这些配置用于过滤上游返回的 IP 地址或对特定 IP 进行特殊处理。 + +* **`bogus-nxdomain `** + * **描述**:定义一个或多个 IP 地址或子网。如果上游 DNS 服务器返回的 A/AAAA 记录的 IP 地址属于这里定义的任何一个 `ip/subnet`,SmartDNS 会将此结果视为一个伪造的 NXDOMAIN (域名不存在) 响应,并可能丢弃该结果或尝试从其他上游获取。这常用于屏蔽运营商的广告推送或 DNS 劫持行为。可以配置多条。 + * **参数**:`` - IP 地址 (如 `1.2.3.4`) 或 CIDR 子网 (如 `1.2.3.0/24`)。 + * **示例**: + ``` + bogus-nxdomain 127.0.0.1 + bogus-nxdomain 0.0.0.0 + bogus-nxdomain 10.0.0.0/8 + ``` + +* **`blacklist-ip `** + * **描述**:定义一个 IP 黑名单。任何解析到的 IP 地址如果位于此黑名单中,将被丢弃。可以配置多条。 + * **参数**:``。 + * **示例**:`blacklist-ip 203.0.113.45` + +* **`whitelist-ip `** + * **描述**:定义一个 IP 白名单。如果一个上游服务器配置了 `-whitelist-ip` 选项,或者全局设置了白名单,那么只有解析结果中的 IP 位于此白名单中才会被接受。可以配置多条。 + * **参数**:``。 + * **示例**:`whitelist-ip 198.51.100.0/24` + +* **`ignore-ip `** + * **描述**:定义需要忽略的 IP 地址。这些 IP 地址在解析结果中会被直接丢弃,不参与测速和选择。可以配置多条。 + * **参数**:``。 + * **示例**:`ignore-ip 224.0.0.1` (一个多播地址) + +* **`ip-alias `** + * **描述**:IP 地址别名。如果解析结果中的某个 IP 地址匹配 ``,SmartDNS 会将其替换为指定的 `` 列表中的一个或多个 IP 地址。这可以用于将内网 IP 映射到公网 IP,或反之,或者将不可路由的 IP 替换为可用的等价 IP。 + * **参数**: + * ``: 要匹配的源 IP 地址或子网。 + * ``: 替换后的一个或多个目标 IP 地址。 + * **示例**:`ip-alias 192.168.1.100 10.0.0.5` + +#### 2.4 测速与优化 + +配置 DNS 解析结果的测速方式和 IP 选择策略。 + +* **`speed-check-mode `** + * **描述**:定义对上游服务器返回的 IP 地址进行存活和速度测试的模式。可以指定多种模式,SmartDNS 会尝试这些模式。 + * **参数**:`` 可以是: + * `ping`: 使用 ICMP ECHO 请求进行 PING 测试。 + * `tcp:`: 尝试与 IP 地址的指定 TCP 端口建立连接。例如 `tcp:80` (HTTP), `tcp:443` (HTTPS)。 + * `none`: 不进行任何主动测速。 + * **默认值**:可能是 `ping` 或 `ping,tcp:80,tcp:443`。 + * **示例**: + ``` + speed-check-mode ping,tcp:80,tcp:443 + speed-check-mode tcp:443 // 只测试 HTTPS 端口 + speed-check-mode none // 完全关闭测速,依赖响应顺序或特定选择模式 + ``` + +* **`force-aaaa-soa [yes|no]`** + * **描述**:当一个域名没有 AAAA 记录(IPv6 地址)时,是否强制返回一个 SOA 记录(表示权威服务器信息,通常客户端会将其解释为 NXDOMAIN 或类似否定应答)而不是简单地不包含 AAAA 记录。这可以防止某些客户端因缺少 AAAA 记录而不断重试。 + * **默认值**:`no`。 + * **示例**:`force-aaaa-soa yes` + +* **`force-qtype-soa [qtype_id | start_id-end_id | ...]`** +* **`force-qtype-soa [-,][qtype_id | start_id-end_id | ...]`** + * **描述**:强制特定 DNS 查询类型 (QTYPE) 在没有对应记录时返回 SOA。 + * **参数**: + * `qtype_id`: DNS 查询类型的数字 ID (例如 A 是 1, AAAA 是 28, HTTPS 是 65)。 + * `start_id-end_id`: 一个范围内的 QTYPE ID。 + * 使用逗号分隔多个类型或范围。 + * 前缀 `-` 表示从已设置的列表中移除指定的 QTYPE。单独一个 `-` 表示清除所有已设置的强制 SOA 类型。 + * **示例**: + ``` + force-qtype-soa 65 // HTTPS (SVCB) 查询无结果时返回 SOA + force-qtype-soa 250-255 // QTYPE 250 到 255 查询无结果时返回 SOA + force-qtype-soa -,65 // 取消对 HTTPS 查询的强制 SOA + ``` + +* **`dualstack-ip-selection [yes|no]`** + * **描述**:是否启用 IPv4/IPv6 双栈 IP 地址选择优化。当一个域名同时拥有 A 和 AAAA 记录时,此功能会尝试选择网络质量更好(例如延迟更低)的 IP 地址类型返回给客户端,或者根据一定的偏好对两者进行排序。 + * **默认值**:`no` (或者可能是 `yes`,具体取决于 SmartDNS 版本和编译选项)。 + * **示例**:`dualstack-ip-selection yes` + +* **`dualstack-ip-selection-threshold `** + * **描述**:当 `dualstack-ip-selection yes` 时,此阈值用于比较 IPv4 和 IPv6 地址的测速延迟。如果 IPv6 的延迟比 IPv4 的延迟低超过这个阈值,可能会优先选择 IPv6,反之亦然。具体比较逻辑可能更复杂。 + * **参数**:`` - 延迟差异阈值,单位毫秒。 + * **默认值**:例如 `0` 或 `20`。 + * **示例**:`dualstack-ip-selection-threshold 50` + +* **`dualstack-ip-allow-force-aaaa [yes|no]`** + * **描述**:(此选项的确切含义需结合代码分析) 可能指在双栈选择过程中,即使 AAAA 记录的测速结果不佳,是否仍然允许在某些条件下(例如没有好的 A 记录)强制选择或包含 AAAA 记录。 + * **默认值**:`yes` 或 `no`。 + * **示例**:`dualstack-ip-allow-force-aaaa yes` + + +#### 2.5 EDNS 和 TTL 配置 + +这些配置涉及 EDNS (Extension Mechanisms for DNS) 客户端子网选项和资源记录的生存时间 (TTL)。 + +* **`edns-client-subnet `** + * **描述**:配置 EDNS0 Client Subnet (ECS) 选项。如果设置了此项,SmartDNS 在向上游 DNS 服务器(支持 ECS 的服务器)发送查询时,会附加指定的 `` 信息。这使得上游服务器能够根据客户端的地理位置(或指定的子网位置)返回更具地理针对性的 DNS 解析结果。可以配置 IPv4 和 IPv6 子网。 + * **参数**:`` - 要在 EDNS 请求中发送的客户端源 IP 地址或子网。例如 `1.2.3.0/24` 或 `2001:db8::/56`。如果只提供 IP,通常会使用该 IP 的 /24 (IPv4) 或 /56 (IPv6) 作为子网。 + * **默认值**:不发送 ECS 信息。 + * **示例**: + ``` + edns-client-subnet 192.0.2.10/24 + edns-client-subnet 2001:db8:1234::/48 + ``` + +* **`rr-ttl `** + * **描述**:为所有 SmartDNS 自己生成的或权威应答中的资源记录设置一个固定的 TTL 值。这会覆盖从上游获取的或规则中定义的 TTL,除非被 `rr-ttl-min` 和 `rr-ttl-max` 限制。 + * **参数**:`` - TTL 值。 + * **默认值**:不设置,使用原始 TTL。 + * **示例**:`rr-ttl 300` (所有记录 TTL 为 5 分钟) + +* **`rr-ttl-min `** + * **描述**:设置 SmartDNS 返回给客户端的资源记录的最小 TTL 值。即使上游返回的 TTL 或计算出的 TTL 小于此值,SmartDNS 也会将其提升到 ``。 + * **参数**:``。 + * **默认值**:例如 `0` 或 `60`。 + * **示例**:`rr-ttl-min 120` (最小 TTL 为 2 分钟) + +* **`rr-ttl-max `** + * **描述**:设置 SmartDNS 返回给客户端的资源记录的最大 TTL 值。即使上游返回的 TTL 或计算出的 TTL 大于此值,SmartDNS 也会将其限制在 ``。 + * **参数**:``。 + * **默认值**:例如 `86400` (一天)。 + * **示例**:`rr-ttl-max 3600` (最大 TTL 为 1 小时) + +* **`rr-ttl-reply-max `** + * **描述**:(此选项与 `rr-ttl-max` 功能可能相似或有细微差别,需代码确认)。通常指在实际回复给客户端的 DNS 报文中,资源记录的 TTL 不会超过此值。 + * **默认值**:例如 `600`。 + * **示例**:`rr-ttl-reply-max 300` + +#### 2.6 限制与响应模式 + +控制 DNS 查询的速率限制和 SmartDNS 选择最终 IP 的行为模式。 + +* **`max-reply-ip-num `** + * **描述**:设置在 DNS 响应中返回给客户端的最大 IP 地址数量。即使 SmartDNS 解析到更多符合条件的 IP 地址,也只会选择最优的 `` 个返回。 + * **参数**:`` - 通常为 1 到 16 之间。 + * **默认值**:`1` 或 `8`。 + * **示例**:`max-reply-ip-num 2` + +* **`max-query-limit `** + * **描述**:设置 SmartDNS 每秒可以处理的最大 DNS 查询请求数量。设置为 `0` 表示没有限制。这是一个基本的防滥用机制。 + * **参数**:``。 + * **默认值**:`0` (无限制)。 + * **示例**:`max-query-limit 1000` + +* **`response-mode `** + * **描述**:定义 SmartDNS 在从多个上游服务器获得多个 IP 地址并进行测速后,如何选择最终返回给客户端的 IP 地址的策略。 + * **参数**:`` 可以是: + * `first-ping`: 选择并发查询中第一个能够被 PING 通的 IP 地址。不一定是最快的,但保证基本可用。 + * `fastest-ip`: 选择所有并发查询结果中,经过测速后延迟最低的 IP 地址。 + * `fastest-response`: 不等待所有并发查询或完整测速,而是当第一个上游服务器返回有效结果时,如果该结果通过了基本的规则检查(如黑白名单),就立即使用这个结果响应客户端。后续的查询结果和测速数据主要用于更新缓存和统计信息。 + * **默认值**:可能是 `fastest-ip`。 + * **示例**:`response-mode fastest-response` + +#### 2.7 日志与审计 + +配置 SmartDNS 的日志记录和审计功能。 + +* **`log-level `** + * **描述**:设置日志输出的详细级别。 + * **参数**:`` 可以是 `off`, `fatal`, `error`, `warn`, `notice`, `info`, `debug`。级别越低,日志越详细 (`debug` 最详细)。 + * **默认值**:`info`。 + * **示例**:`log-level notice` + +* **`log-file `** + * **描述**:指定日志文件的路径。如果未配置,日志可能输出到控制台或 syslog。 + * **参数**:``。 + * **默认值**:无,或如 `/var/log/smartdns/smartdns.log`。 + * **示例**:`log-file /tmp/smartdns.log` + +* **`log-size [k|m|g]`** + * **描述**:设置单个日志文件的最大大小。当达到此大小时,会进行日志轮转(如果 `log-num` > 1)。 + * **参数**:`` 后可跟 `k`, `m`, `g`。 + * **默认值**:例如 `128k`。 + * **示例**:`log-size 10m` + +* **`log-num `** + * **描述**:设置保留的轮转日志文件的数量。当主日志文件达到 `log-size` 时,会重命名为 `log-file.1`,新的日志写入 `log-file`。如果 `log-num` 为 2,则会保留 `log-file.0` (即当前日志) 和 `log-file.1`。设置为 `0` 或 `1` 通常表示不进行或只保留一个文件(无轮转)。 + * **参数**:``。 + * **默认值**:`1` 或 `2`。 + * **示例**:`log-num 5` + +* **`log-console [yes|no]`** + * **描述**:是否将日志信息输出到标准控制台 (stdout/stderr)。当 SmartDNS 在前台运行时此选项比较有用。 + * **默认值**:`no` (当作为守护进程运行时)。 + * **示例**:`log-console yes` + +* **`log-syslog [yes|no]`** + * **描述**:是否将日志信息发送到系统的 syslog 服务。 + * **默认值**:`yes` (在某些系统上)。 + * **示例**:`log-syslog yes` + +* **`log-file-mode `** + * **描述**:设置日志文件的权限模式 (八进制表示)。 + * **参数**:``,如 `644`, `600`。 + * **默认值**:系统默认。 + * **示例**:`log-file-mode 640` + +* **`audit-enable [yes|no]`** + * **描述**:是否启用 DNS 查询审计功能。审计日志通常记录每个 DNS 查询的详细信息,如时间、客户端 IP、查询域名、类型、返回的 IP、使用的上游服务器等。 + * **默认值**:`no`。 + * **示例**:`audit-enable yes` + +* **`audit-SOA [yes|no]`** + * **描述**:当 `audit-enable yes` 时,是否在审计日志中记录那些结果为 SOA 的查询。 + * **默认值**:`no` 或 `yes`。 + * **示例**:`audit-SOA no` + +* **`audit-file `** + * **描述**:指定审计日志文件的路径。 + * **参数**:``。 + * **默认值**:例如 `/var/log/smartdns-audit.log`。 + * **示例**:`audit-file /opt/smartdns/audit.log` + +* **`audit-size`, `audit-num`, `audit-console`, `audit-syslog`, `audit-file-mode`** + * **描述**:这些选项与对应的 `log-*` 选项功能类似,但应用于审计日志。 + +#### 2.8 集成与其他 + +主要涉及与其他服务(如 dnsmasq)的集成或 TLS 相关的全局设置。 + +* **`dnsmasq-lease-file `** + * **描述**:指定 dnsmasq 服务生成的 DHCP 租约文件路径。SmartDNS 可以读取此文件,从而能够解析本地网络中通过 DHCP 获取 IP 的主机名。 + * **参数**:``。 + * **默认值**:通常是 `/var/lib/misc/dnsmasq.leases` 或 `/tmp/dnsmasq.leases`。 + * **示例**:`dnsmasq-lease-file /var/lib/dnsmasq/dnsmasq.leases` + +* **`ca-file `** + * **描述**:指定一个包含受信任 CA 证书的 PEM 文件。此文件用于验证上游 DoT/DoH/DoQ 服务器证书的有效性。 + * **参数**:``。 + * **默认值**:系统 CA 证书包,如 `/etc/ssl/certs/ca-certificates.crt`。 + * **示例**:`ca-file /etc/custom_cas.pem` + +* **`ca-path `** + * **描述**:指定一个包含多个受信任 CA 证书文件(特定命名格式,如哈希值.0)的目录。用于验证上游服务器证书。 + * **参数**:``。 + * **默认值**:系统 CA 证书目录,如 `/etc/ssl/certs`。 + * **示例**:`ca-path /etc/ssl/custom_certs/` + +#### 2.9 上游 DNS 服务器配置 + +定义 SmartDNS 将查询请求转发到的外部 DNS 服务器。这是 SmartDNS 的核心配置部分。 + +* **`server [options...]`** (用于 UDP, 也可自动识别协议) +* **`server-tcp [options...]`** +* **`server-tls [options...]`** (DoT, 默认端口 853) +* **`server-https [options...]`** (DoH, 默认端口 443, URL 如 `https://dns.google/dns-query`) +* **`server-quic [options...]`** (DoQ, 默认端口 853 或 784) +* **`server-h3 [options...]`** / **`server-http3 [options...]`** (DoH3, 默认端口 443) + * **描述**:配置一个上游 DNS 服务器。可以添加多个不同协议和不同地址的服务器。 + * **参数 (基本)**: + * `IP_address_or_URL:[port]`: 服务器的 IP 地址或主机名 (对于 DoT/DoH/DoQ/DoH3)。对于 DoH/DoH3,通常是一个完整的 URL。端口可选,如果省略则使用协议的默认端口。 + * **`options` (可为每个上游服务器单独设置)**: + * `-group [-group ...]`:将此服务器分配到一个或多个服务器组。域名规则中的 `nameserver /domain/group_name` 可以指定使用哪个组。 + * `-exclude-default-group`: 此服务器不属于默认服务器组(即没有被 `-group` 指定的服务器自动构成的组)。 + * `-blacklist-ip`: 使用全局定义的 `blacklist-ip` 列表过滤此服务器返回的结果。 + * `-whitelist-ip`: 使用全局定义的 `whitelist-ip` 列表过滤此服务器返回的结果(即只有在白名单中的 IP 才会被接受)。 + * `-check-edns`: 要求此服务器返回的响应必须包含 EDNS0 记录,否则丢弃结果。 + * `-proxy `: 通过名为 `` 的已配置代理服务器(见 `proxy-server` 指令)连接此上游服务器。 + * `-bootstrap-dns`: 将此服务器标记为引导 DNS 服务器。引导服务器用于解析其他 DoT/DoH/DoQ/DoH3 服务器的主机名。引导服务器自身通常应配置为 IP 地址或通过不需要进一步解析的连接方式。 + * `-set-mark `: (Linux 特定) 在发往此服务器的查询报文上设置一个 netfilter 标记 (fwmark)。 + * `-subnet `: 为发往此特定服务器的查询附加 EDNS Client Subnet 信息,覆盖全局 `edns-client-subnet` 设置。 + * `-host-ip `: 当上游服务器地址是域名时,可以指定一个固定的 IP 地址用于连接,而不是动态解析该域名。 + * `-interface `: 通过指定的网络接口发送到此服务器的查询。 + * `-fallback`: 将此服务器标记为备用服务器。可能在常规服务器查询失败或超时后才使用。 + * **TLS/QUIC/HTTPS 特定选项**: + * `-spki-pin `: 指定一个或多个 Base64 编码的 SHA256 SPKI (Subject Public Key Info) Pin。用于验证服务器证书的公钥,增强安全性,防止中间人攻击。 + * `-tls-host-verify `: 指定在 TLS 握手时要验证的服务器证书中的主机名。通常应与连接的服务器主机名一致。 + * `-host-name `: 指定在 TLS ClientHello 中发送的 SNI (Server Name Indication) 主机名。这对于连接到在同一 IP 上托管多个安全域名的服务器非常重要。 + * `-http-host ` (DoH/DoH3 特定): 指定 HTTP Host 请求头的值。 + * `-k` / `-no-check-certificate`: 禁用对此服务器的 TLS 证书验证(**不推荐,会降低安全性**)。 + * `-alpn ` (DoH3/DoQ特定): 指定 ALPN (Application-Layer Protocol Negotiation) 协议名称,如 `h3` for DoH3, `doq` for DoQ。 + * **示例**: + ``` + server 8.8.8.8 -group google_dns + server-tcp 1.1.1.1:53 + server-tls dns.quad9.net -spki-pin "pin1base64==" -spki-pin "pin2base64==" + server-https https://cloudflare-dns.com/dns-query -host-name cloudflare-dns.com + server-quic quic.adguard-dns.com:784 -alpn doq + server 223.5.5.5 -proxy my_socks_proxy -group alidns + ``` + +* **`proxy-server -name `** + * **描述**:定义一个代理服务器,可供上游 DNS 服务器通过 `-proxy ` 选项使用。 + * **参数**: + * ``: 代理服务器的 URL,格式为: + * SOCKS5: `socks5://[username:password@]host:port` + * HTTP: `http://[username:password@]host:port` + * `-name `: 为此代理配置指定一个唯一的名称。 + * **示例**: + ``` + proxy-server socks5://user1:pass1@10.0.0.1:1080 -name proxy_socks + proxy-server http://10.0.0.2:3128 -name proxy_http + ``` + +#### 2.10 域名级规则 (Domain-Specific Rules) + +这些指令允许针对特定的域名或域名模式进行精细化的控制。 + +* **`nameserver //[|-]`** + * **描述**:为匹配 `` 的域名查询指定使用名为 `` 的上游服务器组。如果使用 `-` 作为组名,则表示忽略对此域名的常规上游查询(其效果可能类似于 `address //#`,即返回 SOA 或不解析,具体行为取决于实现,通常意味着不由 SmartDNS 解析,而是依赖于其他机制或返回否定答案)。 + * **参数**: + * `//`: 域名模式。可以是: + * 精确域名:如 `/www.example.com/` + * 通配符域名:如 `/*.example.com/` (匹配 `example.com` 的所有一级子域名) + * 仅根域名:如 `/-.example.com/` (仅匹配 `example.com` 本身,不匹配子域名) + * `domain-set` 引用:如 `/domain-set:myset/` (匹配 `myset` 域名集合中的所有域名) + * ``: 已定义的上游服务器组名。 + * `-`: 特殊标记,表示不通过常规上游服务器解析此域名。 + * **示例**: + ``` + nameserver /google.com/google_dns_group + nameserver /apple.com/us_dns_group + nameserver /domain-set:streaming_services/fast_dns_group + nameserver /annoying.tracker.com/- + ``` + +* **`address //[|-|#|-4|-6|#4|#6]`** + * **描述**:为匹配 `` 的域名查询直接指定返回的 IP 地址,或进行特殊处理。 + * **参数**: + * `//`: 域名模式,同 `nameserver` 指令。 + * ``: 一个或多个 IPv4 或 IPv6 地址。客户端将收到这些地址。 + * `-`: 表示此域名不由 SmartDNS 直接应答静态 IP,而是向上游服务器查询(如果前面没有 `nameserver` 规则指定特定组,则使用默认组)。这是默认行为,但可用于覆盖更广泛规则的静态地址部分。 + * `#`: 返回一个 SOA 记录,通常客户端会将其视为空结果或域名不存在 (NXDOMAIN)。常用于屏蔽域名。 + * `-4`: 仅向上游查询 AAAA 记录 (IPv6),不查询 A 记录 (IPv4)。如果 AAAA 不存在,行为取决于 `force-aaaa-soa`。 + * `-6`: 仅向上游查询 A 记录 (IPv4),不查询 AAAA 记录 (IPv6)。 + * `#4`: 屏蔽此域名的 A 记录查询 (返回 SOA),AAAA 查询正常。 + * `#6`: 屏蔽此域名的 AAAA 记录查询 (返回 SOA),A 查询正常。 + * **示例**: + ``` + address /my.local.server/192.168.1.100 + address /ads.example.com/# + address /ipv6.only.example.com/-4 + address /domain-set:blocked_sites/# + ``` + +* **`cname //`** + * **描述**:为匹配 `` 的域名查询返回一个 CNAME (Canonical Name) 记录,指向 ``。客户端会随后对 ``发起新的 DNS 查询。 + * **参数**: + * `//`: 源域名模式。 + * ``: CNAME 指向的目标域名。 + * **示例**:`cname /alias.example.com/real.service.example.net` + +* **`srv-record //[][,][,][,]`** + * **描述**:为匹配 `` (通常格式为 `_service._proto.name`) 的 SRV 查询配置 SRV 记录。 + * **参数**: + * `//`: SRV 服务域名模式。 + * ``: 提供服务的主机名。 + * ``: 服务端口号。 + * ``: 记录优先级 (0-65535,越小越优先)。 + * ``:相同优先级记录的权重 (0-65535,用于负载均衡)。 + * 如果只提供服务域名,如 `srv-record /_ldap._tcp.example.com/`,则表示对该 SRV 查询返回 NXDOMAIN 或空结果(具体取决于实现),有效地阻止解析。 + * **示例**: + ``` + srv-record /_ldap._tcp.example.com/ldap-server.example.com,389,10,100 + srv-record /_sip._udp.example.com/sip-proxy.example.com,5060 + ``` + +* **`https-record //[target=...][,port=...][,priority=...][,alpn=...][,ech=...][,ipv4hint=...][,ipv6hint=...]`** + * **描述**:为匹配 `` 的 HTTPS (类型 65,也称 SVCB) 查询配置 HTTPS 记录。 + * **参数**:键值对形式,逗号分隔。 + * `target`: 服务目标主机名 (默认为源域名)。 + * `port`: 服务端口 (默认为 443)。 + * `priority`: 记录优先级 (0-65535,越小越优先,`0` 表示 AliasMode)。 + * `alpn`: 应用层协议协商 ID 列表 (如 `h2,h3`)。 + * `ech`: Encrypted Client Hello 配置。 + * `ipv4hint`: IPv4 地址提示。 + * `ipv6hint`: IPv6 地址提示。 + * `noipv4hint`, `noipv6hint`: 特殊标记,表示不提供 IP 地址提示。 + * **示例**: + ``` + https-record /www.example.com/priority=1,target=svc.example.net,alpn=h2,ipv4hint=192.0.2.1 + https-record /cdn.example.com/noipv4hint,noipv6hint // 表示此 HTTPS 记录仅用于服务参数,不提供 IP 解析提示 + ``` + +#### 2.11 DNS64 配置 + +用于 IPv6 过渡技术。 + +* **`dns64 `** + * **描述**:启用 DNS64 功能,并将指定的 NAT64 前缀用于合成 AAAA 记录。当客户端仅有 IPv6 连接,但请求的域名只有 IPv4 地址 (A 记录) 时,DNS64 服务器会将 A 记录的 IPv4 地址嵌入到 `` 中,合成为一个 AAAA 记录返回给客户端。 + * **参数**:`` - NAT64 IPv6 前缀及其长度,例如 `64:ff9b::/96` (这是标准的 Well-Known Prefix)。 + * **默认值**:禁用。 + * **示例**:`dns64 64:ff9b::/96` + +#### 2.12 ipset 和 nftset 集成 + +将域名解析结果动态添加到 Linux 内核的 ipset 或 nftables set 中,常用于防火墙规则联动。 + +* **`ipset-timeout [yes|no]`** + * **描述**:当 IP 地址被添加到 ipset 时,是否根据 DNS 记录的 TTL 自动设置 ipset 条目的超时时间。如果为 `yes`,当 TTL 到期后,ipset 条目也可能自动移除。 + * **默认值**:`no` (或 `yes`,取决于版本)。 + * **示例**:`ipset-timeout yes` + +* **`ipset //[|#4:|#6:|-|#4:-|#6:-]`** +* **`ipset [|#4:|#6:]`** (全局 ipset) + * **描述**:将匹配 `` 的域名解析到的 IP 地址添加到一个或多个 ipset 集合中。 + * **参数**: + * `//`: 域名模式,或省略表示全局(所有解析结果)。 + * ``: 一个 ipset 名称,IPv4 和 IPv6 地址都会添加到此集合。 + * `#4:`: 仅将 IPv4 地址添加到名为 `` 的 ipset。 + * `#6:`: 仅将 IPv6 地址添加到名为 `` 的 ipset。 + * `-`: 对于此域名模式,禁用 ipset 添加 (即使有全局 ipset 配置)。 + * `#4:-` / `#6:-`: 分别禁用 IPv4/IPv6 地址的 ipset 添加。 + * 可以组合使用 `#4:` 和 `#6:` 来为不同地址族指定不同集合,例如 `ipset /example.com/#4:v4_example,#6:v6_example`。 + * **示例**: + ``` + ipset /gfw.list.example.com/gfwlist_ipset + ipset /video.example.com/#4:video_v4_ipset,#6:video_v6_ipset + ipset global_v4_traffic_set # 全局,只对v4 + ipset /no.ipset.example.com/- + ``` + +* **`ipset-no-speed `** + * **描述**:当一个 IP 地址因为测速失败(例如 PING 不通或 TCP 连接超时)而被 SmartDNS 判定为不可用时,如果配置了此项,该“坏”IP 仍会被添加到一个名为 `` 的特殊 ipset 集合中。这可以用于将这些不良 IP 反馈给防火墙进行特殊处理(例如,阻止它们或记录它们)。 + * **参数**:``。 + * **示例**:`ipset-no-speed bad_ips_set` + +* **`nftset-timeout [yes|no]`** + * **描述**:类似 `ipset-timeout`,但用于 nftables set。 + * **默认值**:`yes`。 + * **示例**:`nftset-timeout yes` + +* **`nftset //[#4:##,#6:#
#]`** +* **`nftset [#4:#
#,#6:#
#]`** (全局 nftset) + * **描述**:将匹配 `` 的域名解析到的 IP 地址添加到 nftables set 中。 + * **参数**: + * `//`: 域名模式,或省略表示全局。 + * `#4:#
#`: 将 IPv4 地址添加到指定的 nftables set。`` (如 `ip`, `arp`, `inet`), `
` (表名), `` (set 名称)。 + * `#6:#
#`: 将 IPv6 地址添加到指定的 nftables set。 + * 同样支持 `-` 和 `#4:-`, `#6:-` 来禁用。 + * **示例**: + ``` + nftset /streaming.example.com/#4:ip#filter_table#video_v4_set,#6:ip6#filter_table#video_v6_set + nftset #4:inet#fw_table#all_v4_traffic_set // 全局,只对v4 + ``` + +* **`nftset-no-speed [#4:##,#6:##]`** + * **描述**:类似 `ipset-no-speed`,但用于将测速失败的 IP 添加到 nftables set。 + * **示例**:`nftset-no-speed #4:ip#main_table#unreachable_ips` + +* **`nftset-debug [yes|no]`** + * **描述**:启用 nftables set 操作的调试日志,有助于排查配置问题。 + * **默认值**:`no`。 + * **示例**:`nftset-debug yes` + +#### 2.13 动态 DNS (DDNS) 和 mDNS + +* **`ddns-domain `** + * **描述**:(此配置的确切功能和实现方式需要详细查阅源码或更专门的文档)。通常 DDNS (Dynamic DNS) 用于当本地公网 IP 地址变化时,自动更新 DNS 服务商处对应域名的 A/AAAA 记录。SmartDNS 中的此配置项可能: + 1. 标记一个本地管理的域名,SmartDNS 可能会监控其 IP 变化并更新(如果 SmartDNS 自身扮演 DDNS 更新客户端的角色)。 + 2. 或者,标记一个需要特殊处理的 DDNS 域名,例如在解析时采用更短的 TTL 或避免缓存。 + * **参数**:`` - 要进行 DDNS 处理的域名。 + * **示例**:`ddns-domain myhome.dyndns.org` + +* **`mdns-lookup [yes|no]`** + * **描述**:是否启用 mDNS (Multicast DNS) 查询功能。如果为 `yes`,当 SmartDNS 收到一个对 `.local` 后缀域名或其他本地名称的查询,且在常规解析流程中未找到结果时,它可能会通过发送 mDNS 查询到本地网络来尝试解析该名称。这用于发现和连接本地网络中的设备和服务 (如打印机、NAS)。 + * **默认值**:`no`。 + * **示例**:`mdns-lookup yes` + +#### 2.14 Hosts 文件 + +* **`hosts-file `** + * **描述**:指定一个或多个自定义的 hosts 文件。SmartDNS 会加载这些文件,并优先使用其中定义的静态 IP-域名映射关系来应答查询,其优先级通常高于上游查询和大部分域名规则 (但可能低于某些强制的 `address /domain/#` 规则)。可以配置多条 `hosts-file` 指令。 + * **参数**:`` - hosts 文件的路径,格式同标准的 `/etc/hosts`。 + * **示例**: + ``` + hosts-file /etc/smartdns/custom_hosts + hosts-file /opt/adblock_hosts_file + ``` + +#### 2.15 高级规则 (`domain-rules`, `domain-set`, `ip-rules`, `ip-set`) + +这些配置提供了更强大和灵活的规则组合能力。 + +* **`domain-rules // [-option value] ...`** + * **描述**:这是一个复合规则指令,允许为匹配 `` 的域名一次性设置多个覆盖全局或组配置的特定行为。 + * **参数**: + * `//`: 域名模式,同 `nameserver` 和 `address`。 + * **`-option value` (可以有多个)**: + * `-speed-check-mode `: 为此域名设置特定的测速模式。 + * `-address [|-|#|...]`: 为此域名设置特定的静态 IP 或屏蔽行为。 + * `-nameserver [|-]`: 为此域名指定特定的上游服务器组。 + * `-ipset []`: 为此域名配置 ipset (格式同全局 `ipset` 的参数部分)。 + * `-nftset []`: 为此域名配置 nftset。 + * `-dualstack-ip-selection [yes|no]`: 为此域名启用/禁用双栈 IP 选择。 + * `-cname `: 为此域名设置 CNAME。 (虽然也可用独立的 `cname` 指令) + * `-https-record `: 为此域名设置 HTTPS 记录。 + * `-rr-ttl `, `-rr-ttl-min `, `-rr-ttl-max `: 设置 TTL。 + * `-no-serve-expired`: 不为此域名提供过期缓存。 + * `-no-cache`: 不为此域名使用缓存。 + * `-no-ip-alias`: 不为此域名应用 IP 别名。 + * `-enable-cache`: (如果 `-no-cache` 是更上层规则) 强制为此域名启用缓存。 + * `-delete`: 删除之前为该 `` 定义的所有 `domain-rules`。 + * `-group `: (元选项) 将此条 `domain-rules` 本身归属到一个规则组,用于更复杂的配置管理,较少直接使用。 + * **示例**: + ``` + domain-rules /fast.example.com/ -speed-check-mode tcp:443 -nameserver high_speed_dns -no-cache + domain-rules /local.resource/ -address 192.168.0.10 -rr-ttl 3600 + domain-rules /domain-set:video_sites/ -ipset video_ipset -dualstack-ip-selection no + ``` + +* **`domain-set -name -type -file `** + * **描述**:定义一个名为 `` 的域名集合。这个集合可以在其他规则 (如 `address`, `nameserver`, `ipset`, `domain-rules`, `client-rules` 的域名匹配部分) 中通过 `/domain-set:/` 的方式引用。 + * **参数**: + * `-name `: 域名集合的唯一名称。 + * `-type `: 集合的类型。 + * `list`: `` 是一个纯文本文件,每行包含一个域名。 + * `geosite`: `` 是一个特定格式的文件(类似简化的 geosite 文件),包含 `full:domain`, `domain:suffix`, `keyword:text`, `regexp:pattern` 这样的条目。SmartDNS 会将 `keyword` 转为包含关键词的正则,`regexp` 直接使用。 + * `geositelist`: 类似 `geosite`,但会忽略 `keyword:` 和 `regexp:` 类型的条目,主要处理 `full:` 和 `domain:`。 + * `-file `: 包含域名列表或 geosite 数据的文件路径。支持 `glob` 通配符。 + * **示例**: + ``` + domain-set -name my_local_domains -type list -file /etc/smartdns/local_domains.txt + domain-set -name cn_domains -type geosite -file /etc/smartdns/geosite_cn.txt + domain-set -name ad_block_keywords -type geosite -file /etc/smartdns/ad_keywords.txt + # 然后在其他地方使用: + # address /domain-set:ad_block_keywords/# + # nameserver /domain-set:cn_domains/china_dns_group + ``` + +* **`ip-rules [-option value] ...`** + * **描述**:针对匹配 `` 的 IP 地址(通常是上游返回的解析结果)应用特定的过滤或修改规则。 + * **参数**: + * ``: 一个 IP 地址或 CIDR 子网,也可以是 `ip-set` 引用。 + * **`-option value`**: + * `-ip-alias `: 将匹配的源 IP 替换为此处的 IP (类似全局 `ip-alias`,但作用域更小)。 + * `-whitelist-ip`: 将匹配的 IP 视为白名单 IP (如果其他地方有白名单检查)。 + * `-blacklist-ip`: 将匹配的 IP 视为黑名单 IP (会被过滤)。 + * `-bogus-nxdomain`: 将包含此 IP 的解析结果视为伪造 NXDOMAIN。 + * `-ignore-ip`: 忽略包含此 IP 的解析结果。 + * **示例**: + ``` + ip-rules 192.168.0.0/16 -ignore-ip // 忽略所有来自这个私有网络的解析结果 + ip-rules ip-set:cdn_provider_ips -ip-alias 127.0.0.1 // 将特定CDN提供商的IP替换为环回地址 + ``` + +* **`ip-set -name -type list -file `** + * **描述**:定义一个名为 `` 的 IP 地址集合。目前 `-type` 仅支持 `list`。此集合可以在 `ip-rules` 或 `client-rules` (用于匹配客户端 IP) 中通过 `ip-set:` 引用。 + * **参数**: + * `-name `: IP 集合的名称。 + * `-type list`: 目前仅支持此类型。 + * `-file `: 包含 IP 地址或 CIDR 子网列表的文件,每行一个。 + * **示例**: + ``` + ip-set -name blocked_tracker_ips -type list -file /etc/smartdns/tracker_ips.txt + # 然后在 ip-rules 中使用: + # ip-rules ip-set:blocked_tracker_ips -blacklist-ip + ``` + +#### 2.16 客户端规则 (Client Rules) + +允许根据 DNS 请求的来源客户端 (IP 地址, MAC 地址或 IP 地址集合) 应用不同的 DNS 处理策略。 + +* **`client-rules [options...]`** + * **描述**:为匹配 `` 的客户端定义一套独立的 DNS 解析选项,这些选项会覆盖全局或 `bind` 端口的默认设置。 + * **参数**: + * ``: 客户端标识。可以是: + * IP 地址:如 `192.168.1.100` + * CIDR 子网:如 `192.168.1.0/24` + * MAC 地址:如 `AA:BB:CC:DD:EE:FF` (SmartDNS 需要能获取到客户端 MAC,通常在同一局域网且直接查询时可行) + * `ip-set` 引用:如 `ip-set:trusted_clients_ips` + * **`[options...]`**: 与 `bind` 指令的 `options` 完全相同。例如: + * `-group `: 此客户端的查询默认使用 `` 服务器组。 + * `-no-speed-check`, `-no-cache`, `-no-rule-addr`, `-no-rule-nameserver`, `-no-rule-ipset`, `-no-rule-soa`, `-no-dualstack-selection` 等。 + * **示例**: + ``` + client-rules 192.168.1.50 -group kids_dns_group -no-cache // 孩子设备使用特定DNS组且不缓存 + client-rules ip-set:iot_devices_ips -no-speed-check -address /domain-set:local_services/192.168.0.1 + client-rules AA:BB:CC:00:11:22 -group gaming_dns + ``` + +#### 2.17 服务器组规则 (Group Rules - 高级) + +提供一种更动态和灵活的方式来定义和匹配服务器组,通常用于非常复杂的场景。 + +* **`group-begin `** + * **描述**:开始定义一个名为 `` 的服务器组的匹配规则块。 +* **`group-match [-g ] [-domain ] [-client-ip ]`** + * **描述**:在 `group-begin` 和 `group-end` 之间使用,定义一条或多条匹配条件。如果请求同时满足 `-domain` (如果提供) 和 `-client-ip` (如果提供) 的条件,则该请求将被路由到 `` (如果提供 `-g` 选项) 或者当前 `group-begin` 定义的组。 + * **参数**: + * `-g ` (可选): 如果匹配,将请求导向此目标组。如果省略,则导向当前 `group-begin` 定义的组。 + * `-domain ` (可选): 匹配请求的域名。 + * `-client-ip ` (可选): 匹配请求的客户端 IP/MAC/IP-Set。 +* **`group-end`** + * **描述**:结束当前服务器组的匹配规则块。 + * **使用场景**: 当简单的 `server ... -group ...` 和 `nameserver /domain/group` 不足以描述复杂的服务器组选择逻辑时使用。例如,可以定义:如果请求来自某个特定客户端 IP 段 *并且* 查询的是特定类型的域名,则使用某个特殊的服务器组。 + * **示例 (概念性)**: + ``` + # 定义上游服务器并分配到基础组 + server 1.1.1.1 -group common_dns + server 8.8.8.8 -group common_dns + server 9.9.9.9 -group secure_dns + + group-begin high_priority_routing + # 如果是来自内网 vip client 对 *.corp.example.com 的请求,使用 secure_dns 组 + group-match -domain /*.corp.example.com/ -client-ip 192.168.1.10 -g secure_dns + # 其他来自内网 vip client 的请求,也使用 secure_dns (更通用的规则放后面) + group-match -client-ip 192.168.1.10 -g secure_dns + group-end + + # 默认情况下,所有其他请求可能走 common_dns (通过 nameserver 或全局默认) + nameserver /.*/common_dns + ``` + * **注意**: `group-begin/match/end` 的具体行为和优先级可能相当复杂,需要仔细测试或查阅非常详细的文档/源码来精确掌握。 + +#### 2.18 插件 (Plugin) + +允许加载外部动态链接库 (.so 文件) 来扩展 SmartDNS 的功能。 + +* **`plugin [plugin_specific_args...]`** + * **描述**:加载一个插件。 + * **参数**: + * ``: 插件共享库文件的路径。 + * `[plugin_specific_args...]`: 传递给插件的参数,格式由插件自身定义。 + * **示例 (SmartDNS UI 插件)**: + ``` + plugin /usr/lib/smartdns/libsmartdns-ui.so + smartdns-ui.www-root /usr/share/smartdns/wwwroot # UI 静态文件根目录 + smartdns-ui.ip http://0.0.0.0:6080 # UI 监听的 HTTP 地址和端口 + # smartdns-ui.ip https://0.0.0.0:6081 # UI 监听的 HTTPS 地址和端口 (如果启用) + # smartdns-ui.cert-file /path/to/ui_cert.pem # UI HTTPS 证书 + # smartdns-ui.key-file /path/to/ui_key.pem # UI HTTPS 私钥 + smartdns-ui.token-expire 3600 # UI 登录 token 过期时间 (秒) + smartdns-ui.max-query-log-age 86400 # UI 查询日志最大保留时间 (秒) + smartdns-ui.enable-terminal [yes|no] # UI 是否启用内置终端功能 + smartdns-ui.enable-cors [yes|no] # UI 是否启用 CORS (跨域资源共享) + smartdns-ui.user # UI 登录用户名 + smartdns-ui.password # UI 登录密码 + ``` + * 插件相关的配置项(如 `smartdns-ui.*`)通常由插件自身定义如何解析和使用,它们不属于 SmartDNS 核心配置指令,但通过 `conf-file` 机制或直接写在主配置文件中传递给插件。 + +### 3. 配置加载机制 + +SmartDNS 的配置加载机制负责在程序启动时读取并解析 `smartdns.conf` 文件(以及通过 `conf-file` 指令间接包含的其他配置文件),将文本化的配置指令转换为程序内部可以使用的数据结构和参数。 + +**核心流程如下:** + +1. **指定主配置文件**: + * SmartDNS 启动时,可以通过命令行参数 `-c ` 指定主配置文件的路径。如果未指定,则使用默认路径 (例如 `/etc/smartdns/smartdns.conf`)。 + +2. **配置文件读取与解析入口**: + * 主程序 (`smartdns_main` 在 `src/smartdns.c`) 调用 `dns_server_load_conf(config_file)` 函数 (可能在 `src/dns_conf/dns_conf.c` 或类似文件中实现) 来启动配置加载过程。 + +3. **配置项定义与注册 (`_config_item[]`)**: + * 在 `src/dns_conf/dns_conf.c` 中,有一个核心的静态数组(通常命名为 `_config_item[]` 或类似)。这个数组扮演着配置指令注册表的角色。 + * 数组的每个元素是一个结构体 (例如 `struct config_item`),它将配置文件中的一个指令字符串 (如 "bind", "server-name", "cache-size") 映射到: + * 一个处理该指令的回调函数(对于复杂指令)。 + * 或者,直接映射到全局配置结构体 `dns_conf` 中某个成员变量的地址和类型信息(对于简单指令)。 + * 这是通过一系列宏来实现的,例如: + * `CONF_STRING("directive", dns_conf.member, max_len)`: 处理字符串类型的配置。 + * `CONF_INT("directive", &dns_conf.member, min_val, max_val)`: 处理整数类型的配置。 + * `CONF_YESNO("directive", &dns_conf.member)`: 处理 "yes" 或 "no" 并转换为布尔值。 + * `CONF_CUSTOM("directive", callback_function, user_data)`: 将指令交给指定的 `callback_function` 处理。 + * `CONF_SIZE`, `CONF_ENUM_FUNC`, `CONF_STRING_LIST` 等其他宏用于不同数据类型和复杂结构。 + +4. **逐行解析与分发**: + * `dns_server_load_conf` 函数会打开并逐行读取配置文件。 + * 对于每一非注释、非空行: + * 提取第一个单词作为指令名。 + * 提取后续单词作为该指令的参数 (通常是空格分隔的 `argc`, `argv` 形式)。 + * 在 `_config_item[]` 注册表中查找与指令名匹配的条目。 + * 如果找到: + * 若是 `CONF_CUSTOM` 类型,则调用其注册的回调函数,并将参数数组 (`argc`, `argv`) 以及可能的 `user_data` 传递给它。例如,`bind` 指令会调用 `_config_bind_ip_udp`,`server` 指令会调用 `_config_server_udp` 等。这些回调函数内部通常使用 `getopt_long_only` 或自定义逻辑来解析更复杂的选项 (如 `-group`, `-no-cache`)。 + * 若是其他类型 (如 `CONF_STRING`, `CONF_INT`),相应的宏处理逻辑会负责从参数中提取值,进行类型转换和验证,并将其存入 `dns_conf` 结构体的指定成员中。 + +5. **全局配置结构体 (`dns_conf`)**: + * 几乎所有的配置解析结果最终都会存储在一个全局的结构体变量中,通常命名为 `dns_conf` (其类型可能是 `struct global_dns_conf`,定义在 `dns_conf.h` 或相关头文件中)。 + * 这个结构体聚合了 SmartDNS 运行所需的所有配置参数,例如: + * `dns_conf.server_name` + * `dns_conf.bind_ip[]` (存储所有 `bind` 指令解析结果的数组) + * `dns_conf.servers[]` (存储所有 `server` 指令解析结果的数组) + * `dns_conf.cache_size`, `dns_conf.cache_persist` + * 各种规则列表或树的根节点 (如 `dns_conf.domain_rules_tree`, `dns_conf.ip_blacklist_tree`)。 + * 默认标志位和行为参数。 + +6. **`conf-file` 指令的处理**: + * 当解析器遇到 `conf-file [-group ]` 指令时,它会: + * 记录当前的上下文信息 (例如,如果指定了 `-group`,则后续从该文件加载的规则属于此组)。 + * 递归地调用配置加载函数来解析 `` 指定的文件。 + * 支持 `glob` 通配符,`_config_foreach_file` 函数用于展开通配符并逐个处理匹配的文件。 + * 解析完被包含的文件后,恢复之前的上下文。 + +7. **错误处理与日志**: + * 在解析过程中,如果遇到无法识别的指令、参数格式错误、值超出范围等情况,SmartDNS 会记录错误日志 (级别和方式由日志配置决定),并可能中止启动或忽略错误的配置行。 + +8. **默认值的应用**: + * 在开始解析配置文件之前,`dns_conf` 结构体的成员会被初始化为预设的默认值。如果在配置文件中没有显式设置某个配置项,则程序会使用其默认值。 + +**总结来说,SmartDNS 的配置加载是一个由注册表驱动的、递归的解析过程。它将结构化的文本配置文件转换为内存中易于访问的全局配置对象 `dns_conf`,后续的 DNS 服务模块将直接读取这个对象中的参数来指导其行为。** + +例如,`bind :53 -group trusted -no-cache` 这一行配置: +1. 指令 `bind` 被识别。 +2. `_config_item[]` 中找到 `bind` 对应的 `_config_bind_ip_udp` (或其他协议变体) 函数。 +3. `_config_bind_ip_udp` 被调用,参数为 `":53"`, `"-group"`, `"trusted"`, `"-no-cache"`。 +4. 函数内部使用 `getopt_long_only` 解析出 IP/端口、`group` 选项的值 "trusted"、`no-cache` 标志。 +5. 这些信息被填充到一个 `struct dns_bind_ip` 实例中,该实例被添加到 `dns_conf.bind_ip[]` 数组。 + +这种机制使得添加新的配置项相对容易:只需在 `_config_item[]` 中添加新的条目,并实现相应的处理函数或在 `dns_conf` 中添加新的成员即可。 + +### 4. 核心服务流程 + +在配置加载完成并存储到 `dns_conf` 后,SmartDNS 进入核心服务流程。这一流程围绕着接收 DNS 请求、处理请求、与上游服务器交互,并最终向客户端返回响应。 + +#### 4.1 服务初始化与监听启动 + +1. **主初始化 (`_smartdns_init` 在 `src/smartdns.c`)**: + * 初始化日志、SSL库、Fast Ping 模块、代理模块、统计模块等基础服务。 + * 调用 `dns_server_init()` (在 `src/dns_server/dns_server.c`) 来初始化 DNS 服务端的核心组件。 + * 调用 `dns_client_init()` (在 `src/dns_client/dns_client.c`) 初始化 DNS 客户端组件,用于向上游发送请求。 + * 调用 `_smartdns_add_servers()` 将 `dns_conf.servers[]` 中配置的上游服务器信息传递给 DNS 客户端模块进行注册和管理。 + * 初始化插件。 + +2. **DNS 服务端初始化 (`dns_server_init`)**: + * 创建 `epoll` 实例:这是 SmartDNS 实现高并发异步网络 I/O 的核心。所有监听套接字和活动的客户端/服务器连接套接字都会注册到这个 `epoll` 实例中。 + * **创建监听套接字 (`_dns_server_socket`)**: + * 此函数遍历 `dns_conf.bind_ip_num` 和 `dns_conf.bind_ip[]` 数组(在配置加载阶段填充)。 + * 对于每一个 `struct dns_bind_ip` 条目,根据其 `type` (UDP, TCP, TLS, HTTPS): + * 调用相应的内部函数 (如 `_dns_server_socket_udp`, `_dns_server_socket_tcp`, `_dns_server_socket_tls`)。 + * 这些函数使用标准的 socket API (`socket()`, `setsockopt()`, `bind()`, `listen()` for TCP/TLS) 来创建和配置监听套接字。 + * 如果配置了 TLS/HTTPS,还会加载 `dns_conf.bind_ca_file` 和 `dns_conf.bind_ca_key_file` 指定的证书和私钥,并创建 SSL/TLS 上下文 (`SSL_CTX`)。 + * 成功创建的监听套接字描述符会被设置为非阻塞模式,并使用 `epoll_ctl()` (通常是 `EPOLL_CTL_ADD`) 添加到 `server.epoll_fd` 中,监听 `EPOLLIN` (可读) 事件。 + * 初始化 DNS 缓存 (`_dns_server_cache_init`):如果启用了持久化缓存,会尝试从 `dns_conf.cache_file` 加载缓存。 + * 启动服务线程/主循环的准备工作。 + +3. **DNS 客户端初始化 (`dns_client_init`)**: + * 创建独立的 `epoll` 实例供客户端模块使用,或者与服务端共享同一个 `epoll` 实例(取决于设计)。 + * 初始化上游服务器列表、服务器组等数据结构。 + * 创建 DNS 客户端工作线程 (`_dns_client_work`)。这个线程通常也基于 `epoll`,负责管理与上游服务器的连接、发送 DNS 请求、接收响应、处理超时以及调用回调函数。 + +4. **进入主事件循环 (`dns_server_run`)**: + * 程序进入一个基于 `epoll_wait()` 的主循环。 + * `epoll_wait()` 会阻塞,直到注册的任何文件描述符上有事件发生(例如,监听套接字上有新连接,或已连接的套接字上有数据到达/可写)。 + +#### 4.2 DNS 请求处理生命周期 + +当一个 DNS 请求到达 SmartDNS 时,其处理过程大致如下: + +1. **接收请求与连接处理 (由 `epoll_wait` 唤醒)**: + * **新连接 (TCP/TLS/HTTPS)**: 如果是监听套接字上的 `EPOLLIN` 事件,表示有新的客户端连接请求。 + * 调用 `accept()` 接受连接,获取新的客户端套接字。 + * 对于 TLS/HTTPS,进行 SSL/TLS 握手 (`SSL_accept()`)。 + * 将新的客户端套接字注册到 `epoll` 中,监听其可读事件。 + * 为连接创建一个上下文结构 (如 `struct dns_server_conn_tcp_client`) 来维护状态。 + * **数据到达 (UDP 或已建立的 TCP/TLS/HTTPS 连接)**: 如果是已连接套接字上的 `EPOLLIN` 事件,表示客户端发送了数据。 + * 从套接字读取数据 (`recvfrom()` for UDP, `SSL_read()`/`read()` for TLS/TCP)。 + * 对于 DoH,还需要解析 HTTP 请求,提取 DNS 报文。 + * 调用 `_dns_server_recv()` (或类似函数) 处理接收到的 DNS 报文。 + +2. **请求解析与初始化 (`_dns_server_recv`)**: + * **解析 DNS 报文头和问题**: 从原始字节流中解析出 DNS 查询 ID, 标志位, 问题数量, 以及第一个问题中的查询域名 (`qname`) 和查询类型 (`qtype`)。存入 `struct dns_packet`。 + * **创建请求上下文 (`_dns_server_new_request`)**: + * 分配并初始化一个 `struct dns_request` 对象。 + * 填充客户端地址信息、查询域名、查询类型、查询 ID、初始配置 (通常是默认配置组)。 + * 关联到当前的客户端连接对象 (`request->conn`)。 + +3. **应用规则与策略 (`_dns_server_setup_request_conf_pre` 和后续逻辑)**: + * **客户端规则匹配**: 根据 `request->conn->client_addr` (客户端 IP),在 `dns_conf.client_rules_tree` (或类似结构) 中查找匹配的客户端规则。如果找到,该规则中定义的选项 (如特定服务器组、no-cache 等) 会覆盖 `request->conf` 中的默认值。 + * **域名规则匹配**: + * **ART 树精确/前缀匹配**: 使用 `request->domain` 在当前生效的规则组 (`request->conf->domain_rule.tree`) 的 ART 树中进行查找。 + * **正则表达式匹配**: 使用 `request->domain` 与已加载的正则表达式列表 (来自 `geosite` 文件的 `keyword:` 和 `regexp:` 条目) 进行匹配。 + * **规则合并与优先级**: 如果同时匹配多种规则 (例如,一个精确域名规则和一个通配符规则,或者 ART 规则和正则规则),需要根据预设的优先级(通常更精确的规则优先)来确定最终应用的规则集。 + * 将匹配到的规则(如特定的 `address` 记录、`nameserver` 组、`ipset` 名称、`no-cache` 标志等)应用到 `request` 对象上,更新其 `conf` 成员或特定字段。 + +4. **处理直接应答规则**: + * **静态 `address` 规则**: 如果匹配到的域名规则是指定了具体 IP 地址 (非 `-`) 或 `#` (SOA): + * 直接根据规则构建 DNS 响应报文。 + * 通过 `_dns_server_reply_address()` (或类似函数) 发送响应。 + * 审计日志。 + * 结束当前请求处理。 + * **静态 `CNAME` 规则**: 如果匹配到 CNAME 规则: + * 在 `request` 对象中记录 CNAME 目标。 + * 将 `request->domain` 修改为 CNAME 目标。 + * 可能会重新进入规则匹配和缓存查找阶段(针对新的 CNAME 目标域名),并有循环检测机制。 + +5. **缓存查找 (`_dns_server_try_cache`)**: + * **条件**: 除非被 `request->conf->flags` (例如 `DNS_CONF_FLAG_NO_CACHE` 来自 `bind` 选项或域名规则) 禁用。 + * 使用 `request->domain` 和 `request->qtype` 作为键,在 DNS 缓存 (通常是哈希表) 中查找。 + * **缓存命中 (未过期)**: + * 从缓存中获取 IP 地址列表、CNAME 记录和 TTL。 + * 构建响应报文。 + * 审计、应用 ipset/nftset (如果适用且之前未应用)。 + * 通过 `_dns_reply_inpacket()` 发送响应。 + * 结束请求处理。 + * **缓存命中 (已过期) 与 `serve-expired`**: + * 如果 `request->conf->dns_serve_expired` 为 `yes`: + * 立即使用过期数据构建响应,并使用 `request->conf->dns_serve_expired_reply_ttl` 作为 TTL。 + * 发送响应。 + * **异步预取**: 如果 `request->conf->dns_prefetch` 也为 `yes`,则为该域名异步发起一次新的上游查询(类似步骤 6),但其结果仅用于更新缓存,不直接响应当前客户端。 + * 结束当前(对客户端的)请求处理。 + * 如果 `serve-expired` 为 `no`,则视为缓存未命中。 + * **缓存未命中**: 继续向上游查询。 + +6. **向上游服务器并发查询 (`_dns_server_do_query` -> `dns_client_query_async`)**: + * **确定上游服务器组**: 根据 `request->server_group_name` (来自域名规则) 或 `request->conf->default_server_group` (来自客户端规则或 `bind` 规则或全局默认)。 + * **选择上游服务器**: 从确定的服务器组中选择一个或多个上游服务器 (`struct dns_server_info`)。选择策略可能包括: + * 全部并发查询 (SmartDNS 的典型做法)。 + * 考虑服务器的健康状态、之前的失败率、权重等。 + * 排除已超时的或暂时不可用的服务器。 + * **构建查询参数**: + * EDNS Client Subnet: 如果 `request->conf->edns_client_subnet` 或特定上游服务器的 `-subnet` 选项已配置,则构造 EDNS0 ECS 选项。 + * **发起异步查询**: 对于每个选定的上游服务器: + * 调用 `dns_client_query_async(server_info, request->domain, request->qtype, options, dns_server_resolve_callback, request)`。 + * `dns_client_query_async` 会将请求加入到 DNS 客户端模块的发送队列。客户端工作线程会负责实际的网络发送 (UDP, TCP, DoT, DoH, DoQ/DoH3 连接和报文封装)。 + * 将 `request` 对象标记为等待上游响应,并可能设置一个总体超时定时器。 + +7. **接收和处理上游响应 (`dns_server_resolve_callback` 被 DNS 客户端模块调用)**: + * 当某个上游服务器返回响应,或者查询超时/出错时,DNS 客户端模块会调用注册的 `dns_server_resolve_callback` 函数,并将 `request` 对象作为用户指针传回。 + * **错误/超时处理**: 如果是错误或超时,记录该服务器的失败,并可能影响其后续被选择的几率。 + * **成功响应**: + * 解析上游返回的 DNS 报文。 + * **IP 过滤**: 应用上游服务器的 `-blacklist-ip`/`-whitelist-ip` 规则,以及全局的 `blacklist-ip`, `whitelist-ip`, `ignore-ip`, `bogus-nxdomain` 和 `ip-rules` 中定义的规则,过滤掉不符合条件的 IP 地址。 + * 将有效的 IP 地址及其 TTL 存入 `request->ip_map` (一个与当前请求关联的临时 IP 地址池)。 + * **CNAME 处理**: 如果上游返回 CNAME 链,记录最终的权威域名,并可能需要对新的权威域名再次发起查询 (如果客户端没有进行 CNAME 追逐)。SmartDNS 通常会处理 CNAME 追逐。 + * **测速触发**: 如果 `request->conf->flags` 未禁用测速,将新收到的、通过过滤的 IP 地址通过 `speed_check_add_ips_from_request(request)` 加入到测速队列。测速模块 (`fast_ping.c`) 会异步进行 PING 或 TCP 探测,并将结果(延迟时间)更新回 `request->ip_map` 中对应 IP 的条目。 + * **响应模式判断**: + * `FASTEST_RESPONSE`: 如果这是第一个到达的有效响应,并且 `request` 尚未被响应,则直接使用此响应(经过滤和简单处理后)通过 `_dns_server_reply_passthrough()` 回复客户端,并缓存结果。请求的主要流程结束,但其他并发查询的结果仍可能用于更新测速数据和缓存。 + * 其他模式 (`FIRST_PING`, `FASTEST_IP`): 继续收集其他并发查询的结果和测速结果。 + +8. **最终决策与响应 (当所有并发查询完成/超时,或满足特定响应模式的条件时)**: + * 此阶段由 `_dns_server_query_end(request)` 或类似逻辑触发。 + * **IP 选择 (`_dns_server_select_ip`)**: + * 根据 `request->response_mode` 和 `request->ip_map` 中包含的 IP 及其测速结果: + * `FIRST_PING`: 从 `ip_map` 中选择第一个 PING 通的 IP。 + * `FASTEST_IP`: 从 `ip_map` 中选择 PING 延迟最低的 IP。 + * **双栈 IP 选择**: 如果 `request->conf->dualstack_ip_selection` 启用,并且 `ip_map` 中同时包含 IPv4 和 IPv6 地址,则应用双栈选择逻辑(比较延迟,考虑阈值 `dns_dualstack_ip_selection_threshold`)来决定是优先返回 IPv4、IPv6,还是两者都返回并排序。 + * 选取不超过 `request->conf->max_reply_ip_num` 数量的 IP 地址。 + * **构建最终响应报文**: 使用选定的 IP 地址(或在无有效 IP 时返回 SOA/NXDOMAIN)填充 DNS 响应的 Answer Section。设置合适的 TTL (考虑 `rr-ttl-*` 规则和上游 TTL)。 + * **审计日志 (`audit_log_request`)**: 记录详细的审计信息。 + * **结果缓存 (`_dns_server_cache_add`)**: + * 如果未禁用缓存,将最终选择的 IP 地址(以及 CNAME 链)和计算出的缓存 TTL 存入全局 DNS 缓存。 + * **ipset/nftset 添加**: 如果 `request->conf` 中有为当前域名(或全局)配置的 `ipset_rule` 或 `nftset_rule`,则将选定的 IP 地址添加到相应的 ipset/nftset 集合中。 + * **发送响应给客户端 (`_dns_reply_inpacket`)**: 将构建好的最终响应报文发送回客户端。 + * **清理资源**: 释放 `request` 对象及其相关资源。 + +这个流程确保了 SmartDNS 能够并发地利用多个上游源,通过测速和规则智能地选择最佳结果,并高效地服务客户端请求。异步 I/O 和多线程(或等效的异步任务)是其高性能的关键。 + + +#### 3.1 配置项定义与解析函数 (`_config_item[]`) + +在 `src/dns_conf/dns_conf.c` 文件中,核心的配置项解析逻辑围绕一个名为 `_config_item[]` 的静态数组展开。这个数组的每一项都代表一个 SmartDNS 支持的配置指令。 + +每个 `struct config_item` 通常包含以下关键成员: + +* `name`: 字符串,表示配置文件中的指令名称,例如 "bind", "server-name", "cache-size" 等。 +* `type`: 枚举类型,指示该配置项的数据类型或特性,例如 `CONFIG_TYPE_STRING`, `CONFIG_TYPE_INT`, `CONFIG_TYPE_SERVER` 等。这有助于解析函数理解如何处理该配置项的值。 +* `offset`: (可选) 通常用于直接将解析后的值存储到全局配置结构体 (`struct dns_config`) 的特定成员中。 +* `parser_func`: 一个函数指针,指向实际处理该配置项的解析函数。当配置文件中的某一行匹配到 `name` 时,这个函数会被调用。 + +**解析流程简述:** + +1. **逐行读取**:`config_load()` 函数 (或其调用的子函数) 逐行读取配置文件。 +2. **指令匹配**:对于每一行,它会尝试与 `_config_item[]` 数组中的 `name` 字段进行匹配,以确定是哪个配置指令。 +3. **调用解析函数**:一旦匹配成功,相应的 `parser_func` 就会被调用。这个函数负责: + * 从该行配置中提取参数值。 + * 验证参数的有效性(例如,检查 IP 地址格式是否正确,端口号是否在允许范围内等)。 + * 将解析后的值存储到程序内部的数据结构中,通常是全局的 `struct dns_config` 实例或其成员指向的其他动态数据结构。 + +#### 3.2 关键配置项的加载示例 + +让我们通过几个例子来看看具体配置项是如何被加载的: + +* **`bind [ip_addr][#port]`**: + * **匹配**: `_config_item[]` 中名为 "bind" 的项。 + * **解析函数**: 类似于 `_config_bind()` (实际函数名可能略有不同)。 + * **过程**: + 1. 函数会解析出 IP 地址和可选的端口号。 + 2. 如果只有 IP 地址,则使用默认端口 (通常是 53)。 + 3. 验证 IP 地址和端口的有效性。 + 4. 将这些监听地址和端口信息存储到一个列表中,供后续创建监听套接字使用。 `struct dns_config` 中会有类似 `listen_addrs` 的成员来存储这些信息。 + +* **`server [#port] [-group ] ...`**: + * **匹配**: "server" (以及 "server-tcp", "server-tls", "server-https" 等变体)。 + * **解析函数**: 例如 `_config_server()`。 + * **过程**: + 1. 解析上游服务器的 IP 地址和端口。 + 2. 解析可选参数,如 `-group` (服务器组)、`-no-speed-check` (禁止测速)、`-no-cache` (禁止缓存此服务器结果)、`-proxy` (指定代理) 等。 + 3. 为每个上游服务器创建一个 `struct server_entry` (或类似) 的结构体实例。 + 4. 将该实例添加到全局的上游服务器列表 (例如 `struct dns_config` 中的 `servers` 列表) 中。 + 5. 如果指定了服务器组,还会将该服务器与对应的组关联起来。 + +* **`cache-size `**: + * **匹配**: "cache-size"。 + * **解析函数**: 可能是通用的整型参数解析函数,或者一个特定的 `_config_cache_size()`。 + * **过程**: + 1. 读取 `` 的值。 + 2. 验证其是否为有效的非负整数。 + 3. 将其转换为字节单位,并存储到 `struct dns_config` 的 `cache_size_bytes` (类似名称) 成员中。 + +* **`domain-rules // -nameserver `**: + * **匹配**: "domain-rules"。 + * **解析函数**: 可能是 `_config_domain_rule()` (实际位于 `src/dns_conf/domain_rule.c` 中的相关函数)。 + * **过程**: + 1. 解析域名模式 (例如,`*.example.com`, `example.net`)。 + 2. 解析关联的动作,例如 `-nameserver` (指定使用特定的上游服务器或服务器组)、`-address` (返回指定的 IP 地址)、`-block` (阻止解析)。 + 3. 这些规则被编译成内部表示(可能是正则表达式或优化的字符串匹配结构),并存储在一个规则列表中 (例如 `struct dns_config` 中的 `domain_rules` 列表)。每个规则项会包含匹配模式和相应的动作及参数。 + +* **`conf-file `**: + * **匹配**: "conf-file"。 + * **解析函数**: 可能是 `_config_conf_file()`。 + * **过程**: + 1. 获取指定配置文件的路径。 + 2. 递归调用主配置加载函数 (`config_load()`) 来加载这个额外的配置文件。这允许用户将配置分散到多个文件中。 + +#### 3.3 默认配置和错误处理 + +* **默认值**: 如果某些配置项在配置文件中没有显式指定,SmartDNS 会使用预设的默认值。这些默认值通常在 `dns_config_init()` (或类似名称的初始化函数) 中设置,或者在解析函数中当参数未提供时应用。 +* **错误处理**: 配置解析过程中,如果遇到语法错误、无效参数或文件读取失败等问题,SmartDNS 会: + * 输出错误信息到日志 (标准输出或指定的日志文件)。 + * 根据错误的严重程度,可能会忽略错误的配置行并继续,或者终止程序启动。例如,一个无法解析的 `bind` 地址可能会导致启动失败,而一个无法识别的域名规则中的参数可能只会被忽略。 + +通过这种结构化的方式,SmartDNS 能够灵活地处理大量配置项,并且易于扩展新的配置指令。开发者只需要在 `_config_item[]` 中添加新的条目,并实现相应的解析函数即可。 + +### 4. 域名服务实现 + +一旦配置加载完成,SmartDNS 就进入服务状态,开始监听指定的 IP 地址和端口,接收并处理 DNS 查询请求。本章将详细阐述域名服务的核心实现流程。 + +#### 4.1 请求的接收与解析 + +DNS 请求的处理始于网络数据包的接收。 + +1. **监听与接收**: + * SmartDNS 根据 `bind` 配置项创建 UDP 和/或 TCP 套接字,并在主事件循环中 (通常使用 `epoll` 或 `select` 等 I/O 多路复用机制) 监听这些套接字上的传入数据。 + * 当有数据到达时,`_dns_server_recv()` (或类似函数,位于 `src/dns_server/dns_server.c`) 被调用。 + * 该函数负责从套接字读取原始的 DNS 请求数据包。 + +2. **请求合法性检查与解析**: + * 接收到的数据包首先会经过初步的合法性检查,例如最小长度、基本格式等。 + * 接着,`dns_request_parse()` (或类似功能的函数,可能在 `src/dns_client/` 或 `src/utils/` 目录下) 会被调用,将原始的二进制 DNS 请求报文解析为程序内部易于处理的结构体 (例如 `struct dns_request` 或 `struct message`)。 + * 这个结构体通常包含: + * **查询 ID (Transaction ID)**: 用于匹配请求和响应。 + * **标志位 (Flags)**: 如 QR (Query/Response), Opcode, RD (Recursion Desired) 等。 + * **问题段 (Question Section)**: 包含查询的域名 (QNAME)、查询类型 (QTYPE, 如 A, AAAA, CNAME, MX, NS, PTR, TXT, SRV, SOA) 和查询类 (QCLASS, 通常是 IN for Internet)。 + * **(可选) 附加记录段 (Additional Records Section)**: 可能包含 EDNS0 相关信息,如客户端子网 (ECS)。 + +3. **请求来源信息**: + * 同时,程序会记录请求的来源 IP 地址和端口,这对于后续的响应发送和某些特定规则(如基于源 IP 的访问控制,虽然 SmartDNS 主要不强调这个)是必要的。 + +#### 4.2 域名匹配与规则应用 + +在成功解析DNS请求后,SmartDNS 会根据配置的规则和内部状态来决定如何处理这个查询。这个过程通常遵循一定的优先级。 + +##### 4.2.1 缓存查找 (Cache First) + +这是最优先的步骤。 + +* **构建缓存键 (Cache Key)**: SmartDNS 会根据查询域名 (QNAME)、查询类型 (QTYPE) 和查询类 (QCLASS) 构建一个唯一的缓存键。如果启用了 EDNS 客户端子网 (ECS) 并且 `cache-ecs-lookup` 为 `yes`,ECS 信息也可能成为缓存键的一部分。 +* **查询缓存**: 使用此键在内部 DNS 缓存中查找。 + * **命中 (Cache Hit)**: 如果找到有效的、未过期的缓存条目: + 1. 检查缓存条目的 TTL 是否仍然有效。 + 2. 如果配置了 `serve-expired yes` 或 `serve-expired-ttl` 相关的选项,并且缓存已过期但仍在宽限期内,SmartDNS 可能会返回这个过期的记录,同时异步地向上游服务器刷新该记录。 + 3. 如果缓存有效,SmartDNS 会直接使用缓存中的 IP 地址列表(或其他类型的记录)来构建响应,并跳过后续的大部分处理步骤。 + 4. 如果配置了 `prefetch-domain yes` 且缓存即将过期,SmartDNS 可能会在返回当前缓存结果的同时,异步向上游请求更新,以保持缓存的“新鲜度”。 + * **未命中 (Cache Miss)**: 如果缓存中没有找到对应的条目,或者条目已过期且不符合 `serve-expired` 的条件,则进入后续的处理流程。 + +##### 4.2.2 域名规则匹配 (`domain-rules`, `address`, `nameserver /domain/`) + +如果缓存未命中,SmartDNS 会检查针对该查询域名的特定规则。这些规则在 `src/dns_conf/domain_rule.c` 中定义和处理。 + +1. **`address //`**: + * 如果查询域名匹配 `` (可以是精确域名、通配符域名),SmartDNS 会直接使用指定的 `` (或 `#` 表示的空地址,通常用于屏蔽) 来应答。 + * 这通常用于自定义解析或屏蔽特定域名。 + * 如果匹配成功,且不是空地址,则构建响应并返回,不再向上游查询。 + +2. **`domain-rules // action [params]`**: + * 这是更通用的规则引擎。查询域名会与 `domain-rules` 中定义的模式进行匹配。 + * 如果匹配成功,则执行相应的 `action`: + * `-nameserver `: 将查询转发给指定的单个上游服务器或服务器组。 + * `-address `: 效果同 `address` 指令。 + * `-block`: 阻止该域名的解析,通常返回 NXDOMAIN 或空应答。 + * `-ttl `: 为此规则匹配到的结果强制设定 TTL。 + * `-ipset //[]`: 将解析到的 IP 地址添加到指定的 `ipset` 集合中。 + * `-nftset //
/`: 将解析到的 IP 地址添加到指定的 `nftables set` 中。 + * 匹配顺序通常按照配置文件中的定义顺序。 + +3. **`nameserver //[|-]`**: + * 这是 `domain-rules` 中 `-nameserver` 动作的简化版。 + * 如果查询域名匹配 ``,则查询将仅发送给指定的上游服务器组 ``。如果组名为 `-`,则表示该域名不走任何配置的 `server`,可能依赖系统 DNS 或返回错误。 + * 如果指定了具体的服务器组,则后续的上游服务器选择将仅限于该组内的服务器。 + +##### 4.2.3 黑白名单和 IP 黑名单 (`blacklist-ip`, `whitelist-ip`, `domain-set`) + +* **`domain-set // -blacklist` / `-whitelist`**: (通过 `set_file.c` 等处理) + * 如果域名匹配 `domain-set` 中标记为 `-blacklist` 的规则,则该域名会被阻止解析。 + * 如果域名匹配 `domain-set` 中标记为 `-whitelist` 的规则,它可能会绕过某些后续的过滤机制(具体行为取决于实现细节和与其他规则的组合)。 +* `blacklist-ip` 和 `whitelist-ip` 配置的 IP 地址或网段在后续上游服务器返回结果后起作用。如果上游返回的 IP 地址在 `blacklist-ip` 中,则该结果会被丢弃。如果启用了 `whitelist-ip` 并且上游返回的 IP 不在白名单中,也可能被丢弃(除非配置了相应的回退策略)。 + +##### 4.2.4 Geosite/Domain-set 匹配 (`domain-set /.../ -file -geosite ...`) + +* `geosite` 文件 (类似于 V2Ray 的 `geosite.dat`) 包含大量的域名列表,通常按国家或用途分类。 +* `domain-set` 指令可以使用 `-file ` 和 `-geosite [:]` 来加载这些预定义的域名列表,并为它们关联动作,如 `-nameserver ` 或 `-block`。 +* 例如,`domain-set /./ -file /etc/smartdns/geosite.dat -geosite cn -nameserver china_dns_group` 会将所有匹配 `geosite.dat` 中 "cn" (中国) 分类的域名查询导向 `china_dns_group` 服务器组。 +* `src/dns_conf/set_file.c` 中的函数负责解析这些列表文件 (包括纯文本列表和 `geosite` 格式)。`_config_set_read_list()` 和 `_config_set_read_geosite()` 等函数会处理这些文件,将其中的域名(或域名模式)加载到内部的数据结构中,并与指定的动作(如转发到特定服务器组、阻止等)关联起来。 + +如果经过以上所有规则检查后,请求既没有被缓存命中,也没有被特定规则直接处理(如 `address` 或 `-block`),那么 SmartDNS 就需要向上游 DNS 服务器查询。 + +#### 4.3 上游服务器选择与并发查询 + +这是 SmartDNS 实现“最快 IP”核心功能的地方。 + +1. **确定候选服务器列表**: + * 如果之前的域名规则 (如 `nameserver /domain/group` 或 `domain-rules /domain/ -nameserver group`) 指定了特定的服务器组,则候选服务器就是该组内的成员。 + * 否则,候选服务器是所有已配置且未被标记为特定域名专用的全局上游服务器。 + * 被 `exclude-default-group yes` 标记的服务器不会参与默认的全局查询。 + +2. **健康检查与测速 (`speed-check-mode`)**: + * SmartDNS 会定期或按需对上游服务器进行健康检查和网络延迟测速。 + * `speed-check-mode` 配置项定义了测速的域名,例如 `ping.tcp.google.com:443,ping.tcp.baidu.com:443`。SmartDNS 会尝试连接这些目标(或进行 DNS 查询)来评估到各个上游服务器的“速度”。 + * 测速结果(通常是响应时间)会与每个上游服务器关联。 + * 如果一个服务器在健康检查中失败次数过多或响应太慢,可能会被临时禁用或降低优先级。 + * `fast_ping` 目录下的代码 (如 `fast_ping.c`) 实现了底层的 ICMP Ping 功能,可用于某些类型的测速。对于 TCP/TLS/HTTPS 服务器,测速可能涉及建立连接或发送简单的探测请求。 + +3. **服务器组策略**: + * 如果查询被导向一个服务器组,SmartDNS 会在该组内进行选择。 + * 服务器组可以有自己的特性,例如某些组可能配置了 `-no-speed-check`,则组内服务器不参与全局测速排序,或者其结果有特定处理方式。 + +4. **并发查询**: + * SmartDNS 不会只选择一个“最快”的上游服务器然后等待其响应。相反,它会向多个(数量可能由 `dualstack-ip-selection-threshold` 等参数间接影响,或有一个内部并发数限制)看似健康且速度较快的候选上游服务器 **并发地** 发送同一个 DNS 查询。 + * 这个并发查询是 SmartDNS 获得最快结果的关键。 + +5. **结果收集与选择**: + * SmartDNS 等待这些并发请求的响应。 + * **首个合法响应优先**: 一般情况下,SmartDNS 会采用最先返回的、合法的、且通过了 IP 黑白名单检查的 DNS 响应。 + * **IP 地址排序与选择 (`fastest-ip`, `dualstack-ip-selection`)**: + * 如果多个上游服务器在短时间内都返回了结果,并且配置了 `fastest-ip yes`,SmartDNS 会比较这些响应中 A/AAAA 记录的 IP 地址。 + * 它会根据之前对这些 IP 地址(或其所在服务器)的测速结果(不仅仅是上游服务器的延迟,也可能包括对目标 IP 本身的直接测速,虽然这更复杂)来选择“最快”的 IP 地址。 + * `dualstack-ip-selection` 和相关阈值 (`dualstack-ip-selection-threshold`) 用于在 IPv4 和 IPv6 地址之间进行选择和排序,例如优先选择延迟更低的 IP 地址,或者在延迟相近时优先 IPv6。 + * 如果收到的响应中包含 CNAME 记录,SmartDNS 通常会递归解析 CNAME 指向的域名,直到获得最终的 A/AAAA 记录。 + * **TTL 处理**: 会根据配置(如 `min-ttl`, `max-ttl`, `rrset-order`) 和上游返回的 TTL 来确定最终响应中的 TTL 值。 + +#### 4.4 结果缓存 + +一旦从上游服务器获得了满意的结果(或者经过了本地规则处理得到了结果),这个结果(包括查询的域名、类型、记录、TTL等)会被存入 DNS 缓存中。 + +* **缓存键**: 与 4.2.1 中描述的相同。 +* **缓存内容**: 包括解析到的 IP 地址列表 (A/AAAA 记录)、MX 记录、CNAME 记录等,以及它们的 TTL。 +* **缓存淘汰**: 当缓存达到 `cache-size` 上限时,会根据一定的淘汰策略(如 LRU - Least Recently Used)移除旧的或不常用的缓存条目。 +* `negative-ttl` 和 `negative-cache` 配置项决定了对 NXDOMAIN (域名不存在) 等否定结果的缓存时间和行为。 + +#### 4.5 EDNS (ECS) 处理 + +* **接收**: 如果客户端请求包含 EDNS0 选项,特别是 ECS (EDNS Client Subnet),SmartDNS 会解析它。 +* **转发**: 根据 `edns-client-subnet` 的配置,SmartDNS 决定是否将 ECS 信息转发给上游服务器。 + * 如果配置了具体的 IP/掩码 (如 `edns-client-subnet 1.2.3.0/24`),则 SmartDNS 会用这个指定的子网信息替换客户端原始的 ECS 信息(或添加 ECS 信息)再向上游转发。这有助于隐藏客户端的真实子网,同时又能为特定地理区域获得优化的 CDN 结果。 + * 如果配置为 `edns-client-subnet delete`,则会移除客户端请求中的 ECS 信息。 + * 如果配置为 `edns-client-subnet preserve yes` (或类似选项),则会尝试保留客户端的 ECS。 +* **缓存**: 如前所述,`cache-ecs-lookup yes` 会使 ECS 信息成为缓存键的一部分,从而为不同子网的客户端缓存不同的结果。 + +#### 4.6 响应构建与发送 + +最后,SmartDNS 将选定的 DNS 记录和相关信息(如 TTL)组装成标准的 DNS 响应报文。 + +1. **构建响应报文**: + * 使用与原始请求相同的查询 ID。 + * 设置标志位 (QR=1表示响应, AA位根据情况设置, RA递归可用位通常会设置)。 + * 填充答案段 (Answer Section) 为选定的 DNS 记录。 + * 如果需要,填充授权段 (Authority Section) 和附加段 (Additional Section)。 +2. **发送响应**: + * 将构建好的响应报文通过之前接收请求的套接字发送回客户端的源 IP 地址和端口。 + +这个流程构成了 SmartDNS 处理每一个 DNS 查询的核心循环。通过积极的缓存、灵活的域名规则、并发的上游查询以及智能的结果选择,SmartDNS 致力于在各种网络环境下提供快速且准确的域名解析服务。 + + +### 5. 高级功能与集成 + +除了核心的 DNS 解析流程,SmartDNS 还提供了一些高级功能以及与系统集成的能力,以增强其可用性和在复杂网络环境中的适应性。 + +#### 5.1 日志系统 + +SmartDNS 提供了灵活的日志记录功能,帮助用户监控其运行状态、诊断问题以及分析 DNS 查询行为。 + +* **`log-level `**: 控制日志输出的详细程度。可选的级别通常包括 `fatal`, `error`, `warn`, `info`, `debug`, `trace`。级别越高,输出的信息越详细。 +* **`log-file `**: 指定日志文件的路径。如果未指定,日志通常输出到标准错误流 (stderr)。 +* **`log-size `**: 当日志文件达到指定大小时,会自动进行轮转 (rotate),旧的日志文件会被重命名(例如,`smartdns.log.1`),并创建新的日志文件。 +* **`log-num `**: 指定保留轮转日志文件的数量。 +* **`log-queries yes|no`**: 是否记录每个 DNS 查询的详细信息。这对于调试特定域名的解析问题非常有用,但会产生大量的日志。 +* **日志内容**: + * 启动和关闭信息。 + * 配置加载过程中的错误和警告。 + * 上游服务器的状态变化(例如,服务器不可达、恢复正常)。 + * DNS 查询处理过程中的重要事件(例如,缓存命中/未命中、特定规则的应用、向上游查询的发送和接收、最终选择的结果)。 + * 错误信息,如解析失败、网络错误等。 + +日志系统的实现在 `src/utils/log.c` (或类似文件) 中,提供了格式化输出、级别控制、文件写入和轮转等功能。 + +#### 5.2 统计与运行时信息 + +SmartDNS 内部会维护一些统计信息,可以通过特定方式查询,以便了解其运行性能和状态。 + +* **查询统计**: + * 总查询次数。 + * 缓存命中次数、命中率。 + * 各类查询类型 (A, AAAA, MX 等) 的数量。 + * 转发到不同上游服务器或服务器组的查询数量。 +* **缓存统计**: + * 当前缓存条目数。 + * 缓存使用的内存大小。 +* **上游服务器状态**: + * 每个上游服务器的延迟、可用性状态、收发包数、错误数等。 +* **获取方式**: + * **信号**: 某些实现中,可以通过向 SmartDNS 进程发送特定信号 (如 `SIGUSR1` 或 `SIGINFO`) 来触发统计信息转储到日志文件。 + * **控制接口/API**: 更高级的实现可能会提供一个简单的控制接口 (例如,通过特定的 TCP/UDP 端口或 Unix domain socket) 来查询运行时信息。 + * **LuCI 界面**: 在 OpenWrt 等路由器固件上,SmartDNS 的 LuCI 界面通常会调用后端的 `ubus` 或 `rpcd` 接口来获取这些统计数据并展示给用户。`package/luci/files/luci/controller/smartdns.lua` 和 `package/luci/files/root/usr/share/rpcd/acl.d/smartdns.json` 等文件与此相关。 + +这些统计信息对于性能调优和监控 SmartDNS 的健康状况至关重要。 + +#### 5.3 `ipset` 与 `nftables` 集成 + +SmartDNS 能够将其解析到的 IP 地址动态地添加到 Linux 内核的 `ipset` 集合或 `nftables set` 中。这对于实现基于域名的高级防火墙规则或策略路由非常有用。 + +* **配置指令**: + * `ipset ///[]` + * `nftset ///
/` + * `domain-rules // -ipset //[]` + * `domain-rules // -nftset //
/` + +* **工作流程**: + 1. 当一个 DNS 查询匹配了配置了 `ipset` 或 `nftset` 动作的域名模式时: + 2. SmartDNS 会正常解析该域名,获取其 A (IPv4) 和/或 AAAA (IPv6) 记录。 + 3. 对于解析到的每一个 IP 地址: + * 它会检查该 IP 地址是否已经存在于目标 `ipset` 集合或 `nftables set` 中。 + * 如果不存在,SmartDNS 会调用相应的命令行工具 (`ipset add ...` 或 `nft add element ...`) 将该 IP 地址添加到指定的集合中。 + * `family` 参数 (如 `ipv4`, `ipv6`) 确保只有对应地址族的 IP 才会被添加。 + 4. 这些 IP 地址也会根据其 TTL 在 `ipset`/`nftables set` 中设置一个超时时间。当 TTL 到期后,这些 IP 地址理论上应该从集合中自动移除(ipset 支持超时,nftables set 的元素管理可能需要外部脚本配合或依赖内核的垃圾回收)。SmartDNS 本身也可能在刷新或移除缓存条目时,尝试从 set 中移除对应的 IP。 + +* **实现**: + * 这通常通过在 SmartDNS 内部调用外部命令 ( `fork` + `exec` ) 来完成。 + * 需要确保 SmartDNS 运行时具有执行 `ipset` 和 `nft` 命令的权限。 + * 错误处理机制会记录调用这些外部命令失败的情况。 + +* **用例**: + * **域名防火墙**: 将特定分类的域名(例如,广告域名、恶意软件域名)解析到的 IP 地址加入到一个 `ipset` 黑名单中,然后用 `iptables` 或 `nftables` 规则 DROP 或 REJECT 前往这些 IP 的流量。 + * **策略路由**: 将访问特定服务(例如,Netflix、Google)的域名解析到的 IP 加入到特定的 `ipset` 中,然后配置策略路由规则,使得访问这些 IP 的流量走特定的网络接口(例如,VPN 接口)。 + +`src/dns_conf/set_cmd.c` (或类似文件) 可能包含了执行这些外部命令的逻辑。 + +#### 5.4 预取 (Prefetching) + +为了减少 DNS 解析延迟,特别是在缓存条目即将过期时,SmartDNS 支持预取功能。 + +* **`prefetch-domain yes|no`**: 控制是否启用域名预取。 +* **工作机制**: + 1. 当一个 DNS 查询命中缓存,并且该缓存条目的 TTL 即将到期(例如,剩余 TTL 小于某个预设的阈值)时: + 2. 如果启用了预取,SmartDNS 会立即将当前缓存的结果返回给客户端,以保证低延迟。 + 3. 同时,它会在后台异步地向上游服务器重新查询该域名,以获取最新的记录。 + 4. 当新的结果返回后,它会更新缓存中的条目。 +* **优点**: 客户端总是能快速得到响应(即使是稍微有点旧的),而缓存则通过后台任务保持更新,避免了在缓存过期瞬间所有客户端请求该域名时都必须等待上游查询完成的情况。 + +#### 5.5 持久化缓存 (Persistent Cache) + +为了在 SmartDNS 重启后能够快速恢复之前的缓存状态,减少冷启动时的延迟,支持将 DNS 缓存保存到磁盘并在启动时加载。 + +* **`cache-persist yes|no`**: 是否启用持久化缓存。 +* **`cache-file `**: 指定持久化缓存文件的路径。 +* **工作机制**: + * **保存**: 当 SmartDNS 正常关闭时 (例如,收到 `SIGTERM` 信号) 或定期地,它会将内存中的有效缓存条目序列化并写入到指定的 `cache-file` 中。 + * **加载**: 当 SmartDNS 启动时,如果 `cache-file` 存在且有效,它会读取文件内容,反序列化这些条目,并将它们加载回内存缓存中。会检查加载的条目的 TTL,过期的条目可能会被丢弃或标记为需要立即刷新。 +* **优点**: 大大缩短了服务重启后的“预热”时间,特别是对于那些拥有大量常用域名缓存的场景。 + +#### 5.6 双栈 IP 选择与第二 DNS 服务 (`dualstack-ip-selection`, `second-dns-server`) + +* **`dualstack-ip-selection optimistic|strict|ipv4_only|ipv6_only`**: 控制在同时获得 IPv4 和 IPv6 地址时的选择偏好。 + * `optimistic`: 可能基于测速结果返回最快的,或者同时返回两者。 + * `strict`: 可能有更严格的偏好,例如,如果 IPv6 可用且速度达标,则只返回 IPv6。 + * `ipv4_only` / `ipv6_only`: 只返回指定类型的 IP 地址。 +* **`dualstack-ip-selection-threshold `**: 当 `dualstack-ip-selection` 模式允许比较 IPv4 和 IPv6 速度时,此阈值用于判断。例如,如果 IPv6 比 IPv4 慢不超过这个毫秒数,可能仍然优先选择 IPv6。 +* **`second-dns-server`**: 这是一个备用或辅助 DNS 解析机制。 + * 如果 SmartDNS 通过其常规的上游服务器和规则无法解析某个域名 (例如,所有上游都超时或返回错误,或者没有匹配的规则导向任何服务器),它可以将这个无法处理的查询转发给 `second-dns-server` (如果已配置)。 + * 这可以作为一种最终的回退机制,例如,将本地无法解析的请求交给一个公共 DNS 或ISP的 DNS。 + * `src/dns_server/second_dns.c` 可能是处理此逻辑的地方。 + +这些高级功能使得 SmartDNS 不仅仅是一个简单的 DNS 转发器,而是一个强大且可定制的 DNS 解析解决方案。 + +### 6. 源代码结构导览 + +现在为你提供一份详尽的源代码结构导览: + +`src` 目录是 SmartDNS 项目的核心源代码所在,包含了实现 DNS 服务器、客户端、缓存、插件等功能的 C 语言代码。 + +**顶层文件 (在 `src/` 目录下):** + +* `main.c`: 程序的入口点,负责解析命令行参数、初始化 SmartDNS 环境并启动服务。 +* `smartdns.c`: SmartDNS 的主逻辑和核心功能实现,包括事件循环、信号处理等。 +* `dns.c`: 处理 DNS 协议相关的核心代码,包括 DNS 报文的解析、封装、记录类型处理等。 +* `dns_cache.c`: 实现 DNS 缓存机制,用于存储查询结果,提高响应速度并减轻上游服务器负担。 +* `dns_plugin.c`: SmartDNS 插件系统的管理和调用接口,允许通过插件扩展 SmartDNS 的功能。 +* `dns_stats.c`: 收集和报告 DNS 查询的各种统计信息,如查询次数、缓存命中率等。 +* `proxy.c`: 实现 DNS 代理功能,负责将客户端的 DNS 查询转发到配置的上游服务器。 +* `regexp.c`: 处理正则表达式相关的匹配和操作,可能用于规则匹配或过滤。 +* `timer.c`: 提供定时器功能,用于调度各种延时任务和超时处理。 +* `tlog.c`: 实现灵活的日志记录功能,用于输出程序运行信息、警告和错误。 +* `Makefile`: SmartDNS 源代码的构建文件,定义了编译规则、依赖关系和构建目标。 +* `.gitignore`: Git 版本控制忽略文件列表,指定了不需要提交到仓库的文件和目录。 + +**子目录:** + +* **`src/utils/`**: 包含各种通用的工具函数和辅助模块。 + * `url.c`: URL 解析和相关操作函数。 + * `nftset.c`: 与 Netfilter set (nftables) 相关的工具函数,用于管理 IP 地址集合。 + * `ssl.c`: 提供 SSL/TLS 加密相关的工具函数,可能用于 DoT/DoH 等安全协议。 + * `stack.c`: 实现通用的栈数据结构。 + * `tls_header_parse.c`: 解析 TLS 协议头部信息。 + * `misc.c`: 包含各种杂项实用函数。 + * `neighbors.c`: 可能与发现网络邻居(如通过 mDNS)相关的功能。 + * `net.c`: 提供网络相关的通用函数,如套接字创建、绑定、发送接收数据等。 + * `capbility.c`: 处理进程能力(capabilities)相关的功能,用于权限管理。 + * `daemon.c`: 实现将程序作为守护进程运行的功能。 + * `dns_debug.c`: 提供 DNS 调试相关的工具和功能,可能包括报文打印、状态检查等。 + * `ipset.c`: 与 IPset 相关的工具函数,用于管理 IP 地址集合。 + +* **`src/lib/`**: 包含项目内部使用的一些库和数据结构实现。 + * `stringutil.c`: 字符串处理工具函数集。 + * `timer_wheel.c`: 实现时间轮算法,用于高效管理大量定时器事件。 + * `idna.c`: 处理国际化域名 (IDNA) 的编码和解码,支持非 ASCII 域名。 + * `nftset.c`: (与 `src/utils` 中的同名文件可能有关联或重复,需要进一步确认其具体作用) + * `radix.c`: 实现基数树数据结构,常用于 IP 地址的高效查找和匹配。 + * `rbtree.c`: 实现红黑树数据结构,一种自平衡二叉查找树,用于有序数据存储。 + * `art.c`: 实现 Adaptive Radix Tree (ART) 数据结构,用于高效的字符串或 IP 地址查找。 + * `bitops.c`: 位操作相关的工具函数。 + * `conf.c`: 处理 SmartDNS 配置文件解析和数据结构管理。 + +* **`src/include/`**: 存放 SmartDNS 项目的公共头文件。 + * `smartdns/`: SmartDNS 特定的头文件可能组织在此子目录下,供其他模块引用。 + +* **`src/http_parse/`**: 处理 HTTP 协议的解析。 + * `qpack.c` / `qpack.h`: 实现 QPACK (HTTP/3 头部压缩格式) 的解析和编码。 + * `http3_parse.c` / `http3_parse.h`: 解析 HTTP/3 协议报文。 + * `http_parse.c` / `http_parse.h`: 通用的 HTTP 协议解析代码,可能作为其他具体版本解析的基础。 + * `http2_parse.c` / `http2_parse.h`: 解析 HTTP/2 协议报文。 + * `http1_parse.c` / `http1_parse.h`: 解析 HTTP/1.1 协议报文。 + +* **`src/fast_ping/`**: 实现快速 ping 功能,用于检测上游 DNS 服务器的可达性和延迟。 + * `wakeup_event.c` / `wakeup_event.h`: 可能用于事件唤醒机制,提高 ping 的响应速度。 + * `ping_udp.c` / `ping_udp.h`: 实现基于 UDP 协议的 ping 检测。 + * `ping_tcp.c` / `ping_tcp.h`: 实现基于 TCP 协议的 ping 检测。 + * `ping_icmp6.c` / `ping_icmp6.h`: 实现基于 ICMPv6 协议的 ping 检测。 + * `ping_host.c` / `ping_host.h`: 处理对特定主机的 ping 操作。 + * `ping_icmp.c` / `ping_icmp.h`: 实现基于 ICMPv4 协议的 ping 检测。 + * `notify_event.c` / `notify_event.h`: 可能用于通知 ping 结果或事件。 + * `ping_fake.c` / `ping_fake.h`: 可能用于模拟 ping 结果,用于测试或特定场景。 + * `fast_ping.c` / `fast_ping.h`: 快速 ping 功能的核心实现和接口定义。 + +* **`src/dns_server/`**: 包含 SmartDNS 服务器端的实现代码。 + * `speed_check.c` / `speed_check.h`: 实现速度检测功能的核心逻辑,根据 ping 结果等选择最佳上游。 + * `soa.c` / `soa.h`: 处理 SOA (Start of Authority) DNS 记录类型。 + * `server_udp.c` / `server_udp.h`: 实现基于 UDP 协议的 DNS 服务器端。 + * `server_tcp.c` / `server_tcp.h`: 实现基于 TCP 协议的 DNS 服务器端。 + * `server_tls.c` / `server_tls.h`: 实现基于 TLS 协议的 DNS (DoT) 服务器端。 + * `server_https.c` / `server_https.h`: 实现基于 HTTPS 协议的 DNS (DoH) 服务器端。 + * `server_socket.c` / `server_socket.h`: 服务器端套接字相关的通用操作。 + * `request_pending.c` / `request_pending.h`: 管理待处理的客户端请求。 + * `rules.c` / `rules.h`: 处理各种 DNS 规则的匹配和应用,如域名规则、IP 规则等。 + * `ptr.c` / `ptr.h`: 处理 PTR (Pointer) DNS 记录类型(反向解析)。 + * `request.c` / `request.h`: 处理客户端 DNS 请求的生命周期,包括接收、解析、处理和响应。 + * `mdns.c` / `mdns.h`: 实现 mDNS (Multicast DNS) 相关的功能。 + * `neighbor.c` / `neighbor.h`: 可能与处理网络邻居的 DNS 查询相关。 + * `prefetch.c` / `prefetch.h`: 实现 DNS 预取功能,提前解析常用域名。 + * `ipset_nftset.c` / `ipset_nftset.h`: 可能结合 IPset 和 nftables 处理服务器端规则。 + * `local_addr.c` / `local_addr.h`: 处理本地地址相关的逻辑。 + * `dualstack.c` / `dualstack.h`: 处理 IPv4/IPv6 双栈相关的逻辑。 + * `ip_rule.c` / `ip_rule.h`: 处理基于 IP 地址的规则。 + * `connection.c` / `connection.h`: 管理服务器端网络连接。 + * `context.c` / `context.h`: SmartDNS 服务器端上下文管理。 + * `dns_server.c` / `dns_server.h`: SmartDNS 服务器端的总体控制和协调。 + * `audit.c` / `audit.h`: 可能用于审计 DNS 查询和响应。 + * `cache.c` / `cache.h`: 服务器端缓存的具体实现。 + * `client_rule.c` / `client_rule.h`: 处理基于客户端 IP 的规则。 + * `cname.c` / `cname.h`: 处理 CNAME (Canonical Name) DNS 记录类型。 + * `address.c` / `address.h`: 处理 IP 地址和相关操作。 + * `answer.c` / `answer.h`: 构建 DNS 响应报文。 + +* **`src/dns_conf/`**: 处理 SmartDNS 配置文件解析和管理。 + * `srv_record.c` / `srv_record.h`: 解析和处理 SRV DNS 记录配置。 + * `speed_check_mode.c` / `speed_check_mode.h`: 处理速度检测模式的配置。 + * `smartdns_domain.c` / `smartdns_domain.h`: 处理 SmartDNS 特定的域名配置。 + * `set_file.c` / `set_file.h`: 处理集合文件(如 IPset 文件)的解析和加载。 + * `server_group.c` / `server_group.h`: 管理上游 DNS 服务器组的配置。 + * `server.c` / `server.h`: 解析和管理单个上游 DNS 服务器的配置。 + * `qtype_soa.c` / `qtype_soa.h`: 处理 SOA 记录在配置中的表示。 + * `ptr.c` / `ptr.h`: 处理 PTR 记录在配置中的表示。 + * `proxy_server.c` / `proxy_server.h`: 处理代理服务器的配置。 + * `proxy_names.c` / `proxy_names.h`: 处理代理名称的配置。 + * `plugin.c` / `plugin.h`: 解析和管理插件的配置。 + * `nftset.c` / `nftset.h`: 处理 nftset 相关的配置。 + * `nameserver.c` / `nameserver.h`: 处理 nameserver 的配置。 + * `ipset.c` / `ipset.h`: 处理 ipset 相关的配置。 + * `ip_set.c` / `ip_set.h`: 处理 IP 集合的配置。 + * `ip_alias.c` / `ip_alias.h`: 处理 IP 别名的配置。 + * `ip_rule.c` / `ip_rule.h`: 解析和管理 IP 规则配置。 + * `host_file.c` / `host_file.h`: 处理 hosts 文件的解析和加载。 + * `https_record.c` / `https_record.h`: 处理 HTTPS DNS 记录配置。 + * `get_domain.c` / `get_domain.h`: 处理获取域名的配置。 + * `group.c` / `group.h`: 处理规则组的配置。 + * `domain_set.c` / `domain_set.h`: 处理域名集合的配置。 + * `domain_rule.c` / `domain_rule.h`: 解析和管理域名规则配置。 + * `dns_conf.c` / `dns_conf.h`: DNS 配置文件的主要解析和管理入口。 + * `dns_conf_group.c` / `dns_conf_group.h`: 处理 DNS 配置中的组。 + * `dhcp_lease_dnsmasq.c` / `dhcp_lease_dnsmasq.h`: 可能与读取 dnsmasq 的 DHCP 租约文件相关。 + * `dns64.c` / `dns64.h`: 处理 DNS64 功能的配置。 + * `ddns_domain.c` / `ddns_domain.h`: 处理 DDNS (动态 DNS) 域名的配置。 + * `cname.c` / `cname.h`: 处理 CNAME 记录在配置中的表示。 + * `conf_file.c` / `conf_file.h`: 处理配置文件的读取和解析。 + * `client_subnet.c` / `client_subnet.h`: 处理客户端子网的配置。 + * `client_rule.c` / `client_rule.h`: 解析和管理客户端规则配置。 + * `bootstrap_dns.c` / `bootstrap_dns.h`: 处理 bootstrap DNS 的配置(用于启动时解析上游服务器地址)。 + * `bind.c` / `bind.h`: 处理绑定地址和端口的配置。 + * `address.c` / `address.h`: 处理 IP 地址在配置中的表示。 + +* **`src/dns_client/`**: 包含 SmartDNS 作为客户端向上游 DNS 服务器发送查询的代码。 + * `wake_event.c` / `wake_event.h`: 可能用于唤醒等待上游响应的事件。 + * `query.c` / `query.h`: 处理向上游服务器发送 DNS 查询的逻辑。 + * `server_info.c` / `server_info.h`: 管理上游 DNS 服务器的信息,如地址、状态、延迟等。 + * `proxy.c` / `proxy.h`: 客户端侧的代理逻辑,可能用于选择通过哪个上游代理查询。 + * `packet.c` / `packet.h`: 客户端侧 DNS 报文的构建和解析。 + * `pending_server.c` / `pending_server.h`: 管理等待上游服务器响应的查询。 + * `group.c` / `group.h`: 处理客户端查询时使用的上游服务器组。 + * `dns_client.c` / `dns_client.h`: SmartDNS DNS 客户端的总体控制和协调。 + * `ecs.c` / `ecs.h`: 处理 ECS (EDNS Client Subnet) 选项。 + * `conn_stream.c` / `conn_stream.h`: 管理基于流的连接(如 TCP, TLS, HTTPS, QUIC)。 + * `client_tls.c` / `client_tls.h`: 实现基于 TLS 协议向上游发送 DNS 查询 (DoT)。 + * `client_udp.c` / `client_udp.h`: 实现基于 UDP 协议向上游发送 DNS 查询。 + * `client_tcp.c` / `client_tcp.h`: 实现基于 TCP 协议向上游发送 DNS 查询。 + * `client_socket.c` / `client_socket.h`: 客户端侧套接字相关的通用操作。 + * `client_quic.c` / `client_quic.h`: 实现基于 QUIC 协议向上游发送 DNS 查询 (DoQ)。 + * `client_mdns.c` / `client_mdns.h`: 实现作为客户端发送 mDNS 查询。 + * `client_http3.c` / `client_http3.h`: 实现基于 HTTP/3 协议向上游发送 DNS 查询。 + * `client_https.c` / `client_https.h`: 实现基于 HTTPS 协议向上游发送 DNS 查询 (DoH)。 + +这份导览应该能帮助你理解 `src` 目录下各个文件和子目录在 SmartDNS 项目中所扮演的角色。 +通过结合配置文件的指令和对这些源代码模块功能的理解,可以更全面地掌握 SmartDNS 的工作原理和内部机制。 + + +## 附录:核心模块实现细节 + +### A.1 DNS 缓存模块 (`src/dns_server/server_cache.c`) + +DNS 缓存是 SmartDNS 性能的关键。其实现需要高效的查找、插入、删除以及内存管理。 + +**A.1.1 数据结构** + +SmartDNS 的缓存模块通常会使用 **哈希表 (Hash Table)** 作为主要的存储结构,以实现 O(1) 的平均查找时间复杂度。 + +* **哈希表**: + * 一个由多个 "桶" (buckets) 组成的数组。 + * 每个桶是一个链表 (or a balanced binary search tree for better worst-case performance) ,用于解决哈希冲突。 +* **缓存键 (Cache Key)**: + * 由查询域名 (QNAME)、查询类型 (QTYPE) 和查询类 (QCLASS) 组合而成。如果 `cache-ecs-lookup yes`,还会包含 EDNS 客户端子网 (ECS) 信息。 + * 这个组合键会被哈希函数处理,得到一个哈希值,用于确定其在哈希表中的桶索引。 +* **缓存条目 (Cache Entry / `struct cache_entry`)**: + * 存储在哈希表中的实际数据单元,通常包含: + * `key`: 完整的缓存键(或其组件),用于在链表内精确匹配。 + * `message`: 指向一个存储DNS响应报文或解析后的记录数据(如 IP 地址列表、CNAME 记录、MX 记录等)的结构体。 + * `ttl`: 该记录的原始生存时间 (Time To Live)。 + * `expire_time`: 该记录的绝对过期时间戳 (通常是 `receive_time + ttl`)。 + * `prefetch_time`: (如果支持预取) 预取触发时间戳。 + * `last_access_time`: 最后访问时间戳 (用于 LRU 淘汰策略)。 + * `ref_count`: (如果使用引用计数) 引用计数器。 + * `flags`: 状态标志 (例如,是否为否定缓存、是否正在预取)。 + * `next`, `prev`: 用于在哈希桶的链表中链接,以及在全局 LRU 链表中链接。 + +* **LRU (Least Recently Used) 链表**: + * 为了在缓存满时有效地进行淘汰,通常会维护一个全局的双向链表,按照缓存条目的最近使用情况排序。 + * 所有缓存条目都会在这个 LRU 链表中。当一个条目被访问时,它会被移动到链表的头部。 + * 当需要淘汰时,链表尾部的条目(即最久未使用的)会被移除。 + +**A.1.2 核心操作** + +1. **缓存查找 (`cache_lookup(key)`)**: + * 计算 `key` 的哈希值,找到对应的哈希桶。 + * 遍历桶内链表,比较每个条目的完整 `key` 是否与查询 `key` 匹配。 + * 如果找到匹配且 `expire_time` 未到期(或符合 `serve-expired` 条件): + * 更新 `last_access_time`。 + * 将该条目移至 LRU 链表头部。 + * 返回缓存的 DNS 记录。 + * 否则,返回未命中。 + +2. **缓存插入 (`cache_insert(key, data, ttl)`)**: + * 首先检查缓存是否已满 (根据 `cache-size` 配置的内存限制或条目数量限制)。 + * **如果已满**: 调用 `cache_evict()` 淘汰一个或多个条目。 + * 计算 `key` 的哈希值,找到对应的哈希桶。 + * 创建一个新的 `struct cache_entry`,填充数据、计算 `expire_time`。 + * 将新条目插入到对应哈希桶的链表头部。 + * 将新条目插入到 LRU 链表的头部。 + * 更新缓存的当前大小/条目数。 + +3. **缓存淘汰 (`cache_evict()`)**: + * 从 LRU 链表的尾部取出一个条目。 + * 从其所属的哈希桶链表中移除该条目。 + * 释放该条目占用的内存。 + * 更新缓存的当前大小/条目数。 + * 重复此过程,直到缓存空间足够。 + +4. **缓存清理 (Periodic Cleanup)**: + * SmartDNS 可能会有一个后台任务或在特定时机(如每次插入前)遍历缓存,主动移除已完全过期的条目 (TTL 已耗尽且不符合 `serve-expired` 条件),以回收内存。这比单纯依赖 LRU 更主动。 + +**A.1.3 持久化缓存 (`cache-persist`)** + +* **保存 (`cache_save_to_disk()`)**: + * 遍历哈希表中的所有有效(未过期或符合持久化条件的)缓存条目。 + * 将每个条目的关键信息 (key, DNS 记录数据, expire_time) 序列化。 + * 写入到 `cache-file` 指定的文件中。格式可以是二进制的,也可以是某种文本格式 (如 JSON,但效率较低)。 +* **加载 (`cache_load_from_disk()`)**: + * SmartDNS 启动时,如果 `cache-file` 存在: + * 读取文件内容,反序列化每个条目。 + * 对于每个加载的条目,检查其 `expire_time` 是否仍然有效。 + * 如果有效,则调用 `cache_insert()` 将其重新加入到内存缓存中。 + +**A.1.4 内存管理** + +* 精确计算每个缓存条目占用的内存,并与 `cache-size` 配置进行比较。 +* 使用高效的内存分配器,减少碎片。 +* 在删除条目时确保所有相关内存都被正确释放。 + +### A.2 测速模块 (`src/dns_server/server_speed.c`, `src/fast_ping/`) + +测速模块的目的是评估到上游 DNS 服务器的“速度”或可用性,以便 SmartDNS 能够优先选择响应更快的服务器。 + +**A.2.1 测速目标与方法** + +* **配置**: `speed-check-mode` 定义了测速的目标和方式。 + * `ping`: 使用 ICMP Echo 请求探测上游 DNS 服务器 IP。 + * `tcp:`: 尝试与上游 DNS 服务器的指定 TCP 端口建立连接。例如 `tcp:53` (标准 DNS), `tcp:853` (DoT)。 + * `udp:`: (较少见,因为 UDP 无连接) 可能发送一个小的 DNS 查询到上游的 UDP 端口。 + * `dot`: 专门针对 DNS over TLS 服务器的探测。 + * `doh`: 专门针对 DNS over HTTPS 服务器的探测,可能是一个 HEAD 请求或一个小的 GET 请求。 + * `host1:port1,host2:port2,...`: 可以配置多个探测目标,SmartDNS 会轮流或并发探测它们。 +* **探测内容**: + * 对于 `ping`,就是标准的 ICMP Echo。 + * 对于 `tcp:`,通常是尝试完成 TCP 三次握手,并可能立即关闭连接。连接建立的时间是主要的衡量指标。 + * 对于 `dot`/`doh`,除了 TCP 连接时间,还可能包括 TLS 握手时间和接收到第一个字节的时间 (TTFB)。 + * 一些实现可能会发送一个简单的、通常能快速响应的 DNS 查询 (例如,查询一个根服务器或一个众所周知的域名) 作为探测。 + +**A.2.2 测速执行** + +1. **周期性/按需测速**: + * SmartDNS 会定期 (例如,每隔几分钟或几秒钟) 对所有(或活跃的)上游服务器进行测速。 + * 也可能在特定事件触发时进行按需测速,例如,当一个服务器连续多次查询失败后。 +2. **并发探测**: + * 为了提高效率,SmartDNS 通常会并发地对多个上游服务器或多个测速目标进行探测,而不是串行进行。 +3. **记录结果**: + * 对于每个服务器,会记录最近几次的测速结果(例如,响应时间 RTT)。 + * 可能会使用加权平均值或滑动窗口平均值来平滑瞬时波动,得到一个更稳定的“速度评分”。 + * 记录连续失败的次数。如果一个服务器连续多次探测失败,可能会被标记为“不可用”或“慢速”,并在一段时间内降低其优先级或暂时停用。 + +**A.2.3 `fast_ping` 模块 (`src/fast_ping/`)** + +* 如果 `speed-check-mode` 包含 `ping`,`fast_ping` 模块用于发送和接收 ICMP Echo 包。 +* 通常需要原始套接字 (raw socket) 权限,这意味着 SmartDNS 可能需要以 root 用户身份运行,或者被授予 `CAP_NET_RAW` 能力。 +* 实现 ICMP 包的构建、发送、超时处理、校验和计算以及响应解析。 + +**A.2.4 与服务器选择的集成** + +* 测速结果(平均响应时间、可用性状态)是上游服务器选择算法的关键输入。 +* 当 SmartDNS 需要向上游查询时,它会优先选择那些当前测速结果显示为“快速”且“可用”的服务器。 +* `dualstack-ip-selection-threshold` 等配置项也会参考这些测速数据来决定是优先 IPv4 还是 IPv6。 + +### A.3 规则匹配 (`src/dns_conf/domain_rule.c`, `src/dns_conf/set_file.c`, `src/dns_server/rules.c`) + +SmartDNS 提供了强大的域名规则匹配系统,包括 `domain-rules`、`address`、`nameserver /domain/` 以及通过 `domain-set` 实现的 `geosite` 和 `geositelist` 功能。 + +**A.3.1 `domain-set` 与 Geosite/Geositelist 文件** + +* **配置 (`domain-set`)**: + * `-name `: 定义集合名称。 + * `-type [geosite|geositelist]`: `geosite` 支持 `domain:`, `full:`, `keyword:`, `regexp:` 前缀;`geositelist` 通常仅支持 `domain:` 和 `full:` (及无前缀默认的域匹配),以优化性能。 + * `-file /path/to/rules.txt`: 指定包含域名规则的文本文件。 +* **规则文件格式**: + * `domain:`: 匹配该域及其所有子域。 + * `full:`: 仅精确匹配该域。 + * `keyword:` (`geosite` only): 内部转换为正则表达式 `^.*.*$`。 + * `regexp:` (`geosite` only): 使用用户定义的正则表达式。 + * 无前缀: 默认为 `domain:`。 +* **协同工作**: `nameserver /domain-set:set_name/group_name` 指令将匹配 `set_name` 集合的域名查询路由到 `group_name`。 + +**A.3.2 规则加载机制** + +1. **`domain-set` 指令解析 (`_config_domain_set` @ `src/dns_conf/domain_set.c`)**: + * 解析 `smartdns.conf` 中的 `domain-set` 行,提取名称、类型、文件路径。 + * 每个 `domain-set` 名称通过哈希(`dns_domain_set_name_table`)关联一个或多个规则文件定义。 +2. **从文件加载规则 (触发式)**: + * 当 `nameserver` 或 `domain-rules` 指令引用一个 `domain-set` 时,会触发加载。 + * `_config_domain_rule_set_each` (@ `src/dns_conf/domain_rule.c`) 遍历指定 `domain-set` 名称下的条目。 + * `_config_domain_rule_each_from_geosite` (@ `src/dns_conf/set_file.c`) 被调用来处理 `geosite` 和 `geositelist` 类型的文件: + * 逐行读取文件,忽略注释和空行。 + * 解析前缀,提取域名/模式。 + * **`geositelist` 特殊处理**: 跳过 `keyword:` 和 `regexp:` 规则。 + * **`keyword:` 转换**: 将 `keyword:text` 转换为正则表达式 `^.*text.*$`,并通过 `dns_regexp_insert()` 添加到全局正则列表。 + * **`regexp:` 处理**: 通过 `dns_regexp_insert()` 直接添加用户定义的正则表达式。 + * **回调传递**: 解析出的域名/模式(包括转换后的 `keyword` 模式)通过回调 `_config_domain_rule_add_callback` -> `_config_domain_rule_add`,最终被添加到核心规则存储中。 + +**A.3.3 域名规则的存储** + +* **核心数据结构: ART (Adaptive Radix Tree)** (`domain_rule.tree`): + * 用于存储 `full:` 和 `domain:` 规则,以及从 `keyword/regexp` 匹配后得到的**模式字符串本身**。 + * 在存入 ART 前,域名键会经过**反转**处理 (例如 `www.google.com` -> `.com.google.www.`)。这使得通过查找反转键的前缀,能高效实现对原始域名后缀的匹配。 +* **规则节点 (`struct dns_domain_rule`)**: ART 中的相关节点会关联一个 `struct dns_domain_rule`,此结构存储该域名模式对应的具体规则动作(如 `nameserver` 指向的组、`address` 指定的 IP 等)。 +* **正则表达式存储**: 编译后的 `keyword:` 和 `regexp:` 规则对象由 `dns_regexp.c` 相关逻辑管理在一个独立的列表或结构中。 + +**A.3.4 域名匹配查询过程 (`_dns_server_get_domain_rule_by_domain_ext` @ `src/dns_server/rules.c`)** + +1. **查询域名预处理**: 待查询域名同样进行反转并添加 `.` 前后缀。 +2. **阶段一: ART 精确及后缀匹配**: + * 使用 `art_substring_walk` 在 ART 中查找所有匹配反转后域名的前缀(即原始域名的精确匹配及所有有效后缀)。 + * `_dns_server_get_rules` 回调收集关联的规则。由于 ART 的特性和域名反转,“最长后缀匹配”优先自然实现(例如 `sub.google.com` 会优先于 `google.com` 的规则,如果两者都定义了)。 +3. **阶段二: 正则表达式匹配 (若 ART 未直接命中所需规则)**: + * 如果第一阶段 ART 查找未找到期望的规则 (例如,没有直接的 `nameserver` 规则,但可能存在正则规则可以应用),并且系统中存在已加载的正则表达式 (`has_regexp()`),则调用 `dns_regexp_match(original_domain, matched_pattern_buffer)`。 + * 此函数将原始查询域名与存储的正则表达式列表逐一比较。 + * **关键点**: 若匹配成功,`dns_regexp_match` 返回**实际匹配上的正则表达式字符串本身**。然后,这个**字符串**(经过反转等处理后)将**再次作为键在 ART 中进行查找** (`art_substring_walk`),以获取与此正则表达式模式绑定的具体规则动作(如 `nameserver` 指向的组)。 +4. **阶段三: 规则整合**: 收集到的规则根据标志位(如忽略特定类型的规则)进行调整,形成最终应用于当前查询的有效规则集。 + +**A.3.5 优先级小结** + +* `full:` 规则优先于 `domain:` 规则,通过 ART 的最长前缀匹配(实为最长后缀匹配)实现。 +* ART 规则 (`full/domain`) 通常优先于正则表达式规则 (`keyword/regexp`),因为 ART 查找在前,且若找到确定性规则可能跳过正则匹配。 +* `regexp:` 与 `keyword:` 规则之间的优先级取决于它们在 `dns_regexp_match` 内部的匹配顺序(通常是插入顺序)。 + + +### A.4 加密 DNS 协议处理 (DoT, DoH, DoQ) + +SmartDNS 作为 DNS 客户端,通过支持 DoT, DoH, DoQ 协议向上游服务器进行加密查询,以增强用户 DNS 查询的隐私和安全性。这些功能的实现依赖于底层的 TLS/QUIC 库以及精密的协议处理逻辑。 + +**通用技术组件**: + +* **TLS 库**: 通常依赖如 **OpenSSL** 或 **GnuTLS**。这些库提供了 TLS 握手、证书验证、数据加解密等核心功能。相关的初始化、上下文创建 (`SSL_CTX_new`, `SSL_CTX_set_verify`), 证书加载 (`SSL_CTX_load_verify_locations`) 和连接 (`SSL_connect`, `SSL_read`, `SSL_write`) 等操作会在专门的 TLS/SSL 工具函数中封装,例如可能位于 `src/utils/ssl_utils.c` 或 `src/utils/tls_stream.c`。 +* **连接管理**: 为了效率,SmartDNS 会对与上游加密服务器的连接进行管理,包括连接复用(Keep-Alive)、连接池(有限数量的并发连接)、空闲超时和错误重试机制。这部分逻辑可能分散在各个协议处理模块或一个统一的连接管理器中(例如 `src/dns_client/connection_manager.c`)。 +* **配置参数**: + * `ssl-verify [yes|no]`: 控制是否验证服务器证书。 + * `ca-path ` / `ca-file `: 指定 CA 证书路径/文件用于服务器证书验证。 + * `-hostname `: 用于 TLS SNI (Server Name Indication) 和证书主机名验证。 + +**A.4.1 DNS over TLS (DoT)** + +* **配置**: `server-tls [#port] [-hostname ] ...` +* **源文件推测**: + * DoT 协议的特定处理逻辑可能位于 `src/dns_client/upstream_dot.c` 或集成在通用的上游处理模块如 `src/dns_client/upstream_io.c` 中,通过协议类型进行区分。 + * TLS 连接的建立和数据收发会调用上述的 TLS 工具函数。 +* **技术实现流程**: + 1. **TCP 连接**: 使用标准套接字 API (`socket`, `connect`) 与上游 DoT 服务器的指定 IP 和端口 (默认为 853) 建立 TCP 连接。此逻辑可能在 `src/utils/net_utils.c`。 + 2. **TLS 握手**: + * 在已建立的 TCP 套接字上,调用 TLS 库函数(如 OpenSSL 的 `SSL_set_fd()` 后跟 `SSL_connect()`) 进行 TLS 握手。 + * 通过 `SSL_set_tlsext_host_name()` (OpenSSL) 设置 SNI (使用配置的 `-hostname` 或服务器 IP)。 + * 证书验证依据 `ssl-verify` 及 `ca-path`/`ca-file` 配置。 + 3. **DNS 报文封装与传输**: + * 根据 RFC 7858,实际的 DNS 查询报文前会附加一个 2 字节的网络序长度字段。 + * 封装后的数据通过 TLS 加密通道发送 (如 `SSL_write()`)。 + 4. **响应接收与解析**: + * 通过 TLS 加密通道接收数据 (如 `SSL_read()`)。首先读取 2 字节长度,然后读取指定长度的 DNS 响应报文。 + 5. **连接关闭/复用**: 查询完毕后,根据连接管理策略,TLS 连接可能被关闭 (`SSL_shutdown`, `close`) 或保持一段时间以备后续查询复用。 + +**A.4.2 DNS over HTTPS (DoH)** + +* **配置**: `server-https [-hostname ] ...` (URL 示例: `https://dns.example.com/dns-query`) +* **源文件推测**: + * DoH 协议逻辑可能在 `src/dns_client/upstream_doh.c` 或通用的上游处理模块。 + * HTTP 报文的构建和解析可能依赖 `src/http_parse/http_client.c` 或 `src/utils/http_utils.c`。 +* **技术实现流程**: + 1. **URL 解析**: 解析配置的 URL,提取主机名、路径和端口 (默认为 443)。 + 2. **TCP 连接与 TLS 握手**: 同 DoT,与从 URL 中提取的主机和端口建立 TCP 连接并完成 TLS 握手。SNI 通常从 URL 的主机名自动设置。 + 3. **HTTP 请求构建**: + * 将原始 DNS 查询报文作为请求体。 + * **`POST` 方法**: + * 请求行: `POST /dns-query HTTP/1.1` (路径从 URL 获取)。 + * 请求头: + * `Host: dns.example.com` (从 URL 获取)。 + * `Content-Type: application/dns-message`。 + * `Accept: application/dns-message`。 + * `Content-Length: `。 + * 可能还有 `User-Agent` 等。 + * **`GET` 方法 (若支持)**: DNS 查询报文经过 Base64URL 编码后作为 URL 的查询参数 (如 `/dns-query?dns=...`)。此时没有请求体。 + * HTTP 请求的构建可能通过 `sprintf` 或专用的 HTTP 消息构建函数完成。 + 4. **发送 HTTP 请求**: 将构建好的 HTTP 请求通过 TLS 加密通道发送。 + 5. **接收与解析 HTTP 响应**: + * 读取 HTTP 响应。首先解析响应行 (检查状态码,如 `200 OK`) 和响应头 (如 `Content-Type: application/dns-message`, `Content-Length`)。 + * 响应体中包含原始的 DNS 响应报文。 + * HTTP 解析器需要处理分块传输编码 (Chunked Transfer Encoding) 如果服务器使用的话。 + 6. **连接管理**: 利用 HTTP/1.1 Keep-Alive 或 HTTP/2 (如果支持) 进行连接复用。 + +**A.4.3 DNS over QUIC (DoQ)** + +* **配置**: `server-quic [#port] [-hostname ] ...` +* **源文件推测**: + * DoQ 协议逻辑可能在 `src/dns_client/upstream_doq.c`。 + * QUIC 连接的建立和流处理会依赖一个外部 QUIC 库的 C API 封装层,例如 `src/utils/quic_client.c`。 +* **技术实现流程**: + 1. **QUIC 库初始化**: 加载并初始化所选的 QUIC 库 (如 `msquic`, `lsquic`, `quiche`)。 + 2. **QUIC 连接建立**: + * 使用 QUIC 库提供的 API 与上游 DoQ 服务器的 IP 和端口 (RFC 9250 推荐默认端口 853,但早期实现可能用 784) 建立 QUIC 连接。 + * QUIC 连接建立过程中自动完成 TLS 1.3 握手。SNI (使用配置的 `-hostname` 或服务器 IP) 和证书验证是此过程的一部分。 + 3. **创建 QUIC 流**: + * 对于每个 DNS 查询,通过 QUIC API 在已建立的连接上打开一个新的双向流 (bidirectional stream)。 + 4. **DNS 报文封装与传输**: + * 根据 RFC 9250,DNS 报文前同样需要附加一个 2 字节的网络序长度字段。 + * 封装后的 DNS 查询报文写入 QUIC 流。 + 5. **响应接收与解析**: + * 从 QUIC 流中读取数据。首先读取 2 字节长度,然后读取 DNS 响应报文。 + 6. **流与连接管理**: + * 一个 DNS 事务完成后,对应的 QUIC 流关闭。 + * QUIC 连接可以保持打开状态,并发处理多个 DNS 查询(每个在独立的流上),直到空闲超时或显式关闭。 + +### A.5 IPv4/IPv6 双栈优化处理流程 + +SmartDNS 通过一系列配置和内部逻辑来优化在双栈网络环境下的域名解析,目标是根据用户策略选择合适的 IP 地址族并返回最快的结果。 + +**相关配置**: + +* `dualstack-ip-selection `: 核心策略,如 `optimistic`, `ipv4_only`, `ipv6_preferred` 等。 +* `dualstack-ip-selection-threshold `: 比较不同地址族速度时的容忍阈值。 +* `fastest-ip [yes|no]`: 是否在同一地址族的多个 IP 中选择最快的。 + +**源文件推测**: + +* **查询发起**: `src/dns_server/server_query.c` 或 `src/dns_client/resolver.c` 负责向上游服务器发起查询,可能包含并发请求 A 和 AAAA 记录的逻辑。 +* **结果处理与选择**: `src/dns_server/server_response.c` 或 `src/dns_client/upstream_process.c` (或类似文件) 会包含处理上游响应、聚合结果,并根据双栈配置进行筛选和排序的逻辑。 +* **配置读取**: 全局配置(包括双栈设置)通常存储在 `struct dns_config` 中,由 `src/dns_conf/dns_conf.c` 初始化和填充。 +* **最终响应构建**: `src/dns_server/dns_server.c` 或其调用的响应构建模块,根据选择结果填充 DNS 应答报文。 + +**技术实现细节**: + +1. **并发查询发起**: + * 当收到客户端查询时,如果 `dualstack-ip-selection` 模式不是 `*_only`,SmartDNS 的查询模块会为该域名生成两个并行的内部子查询:一个请求 A 记录,一个请求 AAAA 记录。 + * 这些子查询会通过上游服务器选择逻辑(考虑测速结果、服务器组等)分发给一个或多个上游服务器。 + * 实现并发可能通过非阻塞 I/O (如 `epoll` 配合多个套接字操作) 或一个轻量级任务/事件系统。每个子查询的状态(等待响应、已收到、超时)会被跟踪。 + +2. **结果收集与初步处理**: + * 当上游响应到达时,会被解析。A 记录和 AAAA 记录及其 TTL、来源服务器信息 (包括该服务器的 RTT 评估) 会被临时存储,通常与原始客户端请求关联起来。 + * 可能的数据结构:一个请求上下文结构体 (`struct client_request`) 中包含两个列表或数组,分别存放收集到的 A 记录和 AAAA 记录,每个记录项 (`struct ip_result`) 包含 IP 地址、TTL、以及一个指向来源 `struct server_entry` (含 RTT) 的指针或其 RTT 值。 + +3. **执行 `dualstack-ip-selection` 逻辑**: + * 在 `src/dns_server/server_response.c` (或类似模块) 中,会有一段核心的条件判断逻辑,根据 `g_config.dualstack_ip_selection_mode` 的值执行不同分支: + * **`ipv4_only`**: 清空收集到的 AAAA 记录列表。 + * **`ipv6_only`**: 清空收集到的 A 记录列表。 + * **`ipv4_preferred`**: + * 检查 A 记录列表是否为空。若非空,则优先使用 A 记录。 + * 若 A 记录列表为空,则检查 AAAA 记录列表是否为空。若非空,则使用 AAAA 记录。 + * (高级实现): 若两者皆有,且配置了阈值,可能会比较 AAAA 的平均/最佳 RTT 是否显著优于 A 的 RTT (例如 `avg_rtt_aaaa < avg_rtt_ipv4 - g_config.dualstack_ip_selection_threshold`),如果差距巨大,即使是 `ipv4_preferred` 也可能考虑使用 AAAA (但这不常见,通常 "preferred" 意味着更强的偏好)。 + * **`ipv6_preferred`**: 逻辑与 `ipv4_preferred` 对称。 + * **`optimistic` / `strict`**: + * 如果只有 A 或只有 AAAA 记录,则使用可用的。 + * 如果两者都有,计算 A 记录集合中的“最佳RTT_A”和 AAAA 记录集合中的“最佳RTT_AAAA”(可能取平均或最好值)。 + * 比较 `RTT_A` 和 `RTT_AAAA`。 + * 如果 `abs(RTT_A - RTT_AAAA) <= g_config.dualstack_ip_selection_threshold` (速度相近): + * `optimistic` 模式:可能选择两者都返回,或根据一个次要偏好(如配置的全局偏好或随机)选择一个。 + * `strict` 模式:通常有一个固定的偏好,比如优先 IPv6。如果 `RTT_AAAA` 在阈值内不比 `RTT_A` 差,则选择 IPv6,否则选择 IPv4。 + * 如果速度差异超出阈值,则选择 RTT 更小的一方。 + +4. **执行 `fastest-ip` 逻辑**: + * 在上述步骤确定了要返回的一个或两个地址族后,如果 `g_config.fastest_ip` 为 `yes`: + * 对于每个选定的地址族(例如 A 记录),如果其结果列表 `list_of_A_records` 中有多个 IP 地址,会遍历这个列表。 + * 每个 `struct ip_result` 中应有其来源服务器的 RTT。根据这个 RTT 对 `list_of_A_records` 进行排序或选出 RTT 最低的那个 IP 地址(或前 N 个)。 + * 最终只保留“最快”的一个或几个 IP 地址用于构建响应。 + +5. **响应构建**: + * 将经过筛选和排序后的 A 和/或 AAAA 记录(及其对应的 TTL)填充到 DNS 响应报文的 Answer Section。 + * 如果最终没有可用的 IP 地址,则根据情况返回 NXDOMAIN 或 SERVFAIL。 + +通过上述流程,SmartDNS 在 C 代码层面结合配置参数、网络测速数据和条件逻辑,实现了灵活且以性能为导向的双栈 IP 地址选择。代码中会大量使用指针操作、结构体以及条件分支来高效地处理这些规则和数据。 + +### B.1 从配置加载到处理一个 DNS 请求的核心流程 + +```mermaid +graph TD + subgraph 用户/客户端 + Client[DNS 客户端] + end + + subgraph SmartDNS 服务器 + direction TB + Startup["启动程序 (main.c)"] --> ConfigLoad["配置加载 (dns_conf.c)"] + ConfigLoad --> ConfigData[内部配置数据结构] + + subgraph 核心服务循环 + EventLoop["网络事件循环 (epoll/poll)"] + EventLoop --> |收到 DNS 请求| RequestReceive["接收 & 解析请求 (dns_server.c)"] + RequestReceive --> QueryProcess["查询处理流程 (dns_server.c)"] + end + + ConfigData --> QueryProcess + ConfigData --> RuleMatch["规则匹配 (domain_rule.c, set_file.c)"] + QueryProcess --> RuleMatch + + RuleMatch --> UpstreamSelect[上游服务器选择] + UpstreamSelect --> FastPing["测速模块 (fast_ping)"] + FastPing --> UpstreamSelect + + UpstreamSelect --> QueryForward["转发查询 (UDP, TCP, DoT, DoH, DoQ)"] + QueryForward --> UpstreamServer[上游 DNS 服务器] + + UpstreamServer --> ResponseReceive[接收上游响应] + ResponseReceive --> ResponseProcess["处理响应 (IPv4/IPv6 优化)"] + ResponseProcess --> CacheUpdate["更新缓存 (server_cache.c)"] + CacheUpdate --> |返回客户端| Client + CacheUpdate --> Cache[DNS 缓存] + Cache --> |查询| QueryProcess + + QueryProcess --> Log[日志系统] + RuleMatch --> Log + UpstreamSelect --> Log + QueryForward --> Log + ResponseProcess --> Log + CacheUpdate --> Log + end + + style ConfigData fill:#f9f,stroke:#333,stroke-width:2 + style Cache fill:#f9f,stroke:#333,stroke-width:2 + style FastPing fill:#f9f,stroke:#333,stroke-width:2 + style RuleMatch fill:#f9f,stroke:#333,stroke-width:2 + style QueryProcess fill:#ccf,stroke:#333,stroke-width:2 + style RequestReceive fill:#ccf,stroke:#333,stroke-width:2 + style ResponseReceive fill:#ccf,stroke:#333,stroke-width:2 + style ResponseProcess fill:#ccf,stroke:#333,stroke-width:2 + style UpstreamSelect fill:#ccf,stroke:#333,stroke-width:2 + style QueryForward fill:#ccf,stroke:#333,stroke-width:2 +``` + +**图示说明:** + +1. **启动与配置加载**: 程序启动时(`Startup`),首先进行配置加载(`ConfigLoad`),读取 `smartdns.conf` 并解析,将配置信息存储在内部的数据结构(`ConfigData`)中,这些数据结构包括上游服务器列表、各种规则(如 `domain-set`、`geosite` 规则等)。 +2. **核心服务循环**: SmartDNS 进入一个网络事件循环(`EventLoop`),监听来自客户端的 DNS 请求。 +3. **请求接收与处理**: 当收到 DNS 请求时,`RequestReceive` 模块负责接收和初步解析。然后进入核心的查询处理流程(`QueryProcess`)。 +4. **规则匹配**: 在 `QueryProcess` 中,会根据请求的域名应用各种配置规则(`RuleMatch`)。`RuleMatch` 模块会利用加载好的 `ConfigData` 中的规则数据结构进行匹配。特别是 `domain-set` 和 `geosite` 规则,它们有特定的匹配算法和数据结构。 +5. **上游服务器选择与测速**: 根据规则匹配的结果和 `ConfigData` 中的上游服务器列表,决定向哪些上游服务器发送查询。`UpstreamSelect` 模块可能会调用 `FastPing` 模块进行实时测速,以选择最优的上游服务器。 +6. **查询转发**: `QueryForward` 模块负责将 DNS 查询按照相应的协议(UDP, TCP, DoT, DoH, DoQ 等)发送到选定的上游 DNS 服务器(`UpstreamServer`)。 +7. **响应接收与处理**: `ResponseReceive` 模块接收来自上游服务器的响应。`ResponseProcess` 模块处理这些响应,例如进行 IPv4/IPv6 双栈优化(根据测速结果和配置,选择返回 IPv4 或 IPv6 地址)。 +8. **缓存管理**: 处理后的响应会用于更新 DNS 缓存(`CacheUpdate`),并将结果存储在 `DNS 缓存` 中。后续对相同域名的查询可能会直接从缓存中获取(`Cache` -> `QueryProcess`),提高响应速度。 +9. **发送响应**: 最终,处理好的响应会发送回客户端(`Client`)。 +10. **日志**: 整个流程中的关键步骤都会由 `日志系统` 进行记录,方便调试和监控。 + + +### B.2 domain-set 规则匹配流程图 + +这份图将侧重于一个传入的 DNS 查询是如何通过 ART 和正则表达式匹配机制,最终确定应用哪些规则的流程。 + +```mermaid +graph TD + A[DNS 查询: 域名] --> B(("预处理域名
(反转, 加点)")) + + subgraph 规则匹配流程 + B --> C{"ART 查找\n基于反转域名
(art_substring_walk)"} + C --> D["收集 ART 匹配规则
(_dns_server_get_rul)"] + + D --> E{"ART 是否直接命中
精确或后缀规则?"}; + + E -- 是 --> H[整合 ART 规则]; + E -- 否 --> F{"是否存在
已加载的正则规则?"}; + + F -- "否" --> H; + %% 如果没有正则规则,直接整合 ART 规则 (可能为空) + + F -- 是 --> G["正则匹配
基于原始域名
(dns_regexp_match)"]; + + G --> I{"正则是否匹配成功?"}; + + I -- 是 --> J[获取匹配的
正则表达式字符串]; + J --> K{"ART 查找
基于匹配的正则字符串"}; + K --> L[收集与匹配正则关联的规则]; + L --> M[整合 ART + 正则规则]; + %% 合并从两个阶段收集到的规则 + + I -- 否 --> H; + %% 如果正则未匹配,整合仅从第一阶段 ART 收集的规则 (可能为空) + + H --> N["应用规则忽略标志
(_dns_server_update_rule_by_flags)"]; + M --> N; + end + + N --> O[生成最终适用的\n域名规则集]; + + O --> P{"根据规则集选择\n上游服务器或应用动作"}; + P --> Q[发送查询到上游\n或返回本地地址等]; + %% 后续流程... + Q --> R[接收上游响应]; + R --> S["处理响应\n(缓存, 测速, 排序)"]; + S --> T[返回最终结果给客户端]; + + %% 定义样式 (可选,为了图更清晰) + classDef decision fill:#f9f,stroke:#333,stroke-width:2px; + class E,F,I decision; +``` + +**图示说明:** + +1. **DNS 查询**: 流程从 SmartDNS 接收到一个用户的 DNS 查询请求开始。 +2. **预处理域名**: 查询的域名会被反转并添加点前缀/后缀,以适应 ART 的后缀匹配查找。 +3. **ART 查找**: 使用反转后的域名在 SmartDNS 内部存储 `full:` 和 `domain:` 规则的 ART (Adaptive Radix Tree) 中进行查找。 +4. **收集 ART 匹配规则**: 查找过程中,会收集所有匹配该域名的后缀(对应于 ART 中的前缀)所关联的规则。最长后缀匹配的规则通常会优先或覆盖短后缀规则。 +5. **ART 是否直接命中**: 判断第一阶段的 ART 查找是否直接找到了适用于当前域名的规则。 +6. **是否存在已加载的正则规则**: 如果 ART 没有直接命中(或者需要检查更广范围的规则),并且 SmartDNS 中加载了 `keyword:` 或 `regexp:` 类型的正则表达式规则,则进入正则匹配阶段。 +7. **正则匹配**: 使用原始域名与所有已加载的正则表达式进行匹配尝试。 +8. **正则是否匹配成功**: 判断是否有任何正则表达式匹配了当前域名。 +9. **获取匹配的正则表达式字符串**: 如果匹配成功,获取实际匹配上的那个正则表达式字符串。 +10. **ART 查找(基于匹配的正则字符串)**: 使用这个匹配到的正则表达式字符串作为键,再次在 ART 中查找,以获取与该特定正则模式关联的规则(例如 `nameserver /regexp:^.*google.*$/group_a` 这样的配置)。 +11. **收集与匹配正则关联的规则**: 收集第二阶段 ART 查找得到的规则。 +12. **整合规则**: 将第一阶段 ART 收集到的规则和第二阶段(如果进行了且有匹配)正则关联的规则进行整合。 +13. **应用规则忽略标志**: 根据规则中可能包含的忽略标志(如 `ignore-nameserver`),调整最终的规则集。 +14. **生成最终适用的域名规则集**: 得到经过所有匹配和过滤后,最终适用于当前查询域名的规则集合。 +15. **根据规则集选择上游服务器或应用动作**: SmartDNS 根据这个最终规则集,决定将查询发送到哪个上游服务器组,或者应用 `address`、`bogus-nxdomain` 等其他动作。 +16. **发送查询/返回结果**: 执行上一步决定的动作(发送查询、返回本地地址等)。 +17. **后续流程**: 图中简单表示了接收响应、处理(缓存、测速、排序)和返回结果的后续步骤。 + +这个图详细展示了 SmartDNS 如何结合 ART 高效处理 `full`/`domain` 规则以及如何通过正则匹配和二次 ART 查找处理 `keyword`/`regexp` 规则,最终确定一个查询适用的规则集合。 diff --git a/src/.gitignore b/src/.gitignore old mode 100644 new mode 100755 index d69698e3c6..17d8520095 --- a/src/.gitignore +++ b/src/.gitignore @@ -3,3 +3,4 @@ .DS_Store .swp. smartdns +!smartdns/ diff --git a/src/Makefile b/src/Makefile old mode 100644 new mode 100755 index d21c7be375..fef5906b25 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,11 +17,12 @@ BIN=smartdns SMARTDNS_LIB=libsmartdns.a SMARTDNS_TEST_LIB=libsmartdns-test.a -OBJS_LIB=$(patsubst %.c,%.o,$(wildcard lib/*.c)) -OBJS_MAIN=$(filter-out main.o, $(patsubst %.c,%.o,$(wildcard *.c))) -TEST_OBJS=$(patsubst %.o,%_test.o,$(OBJS_MAIN) $(OBJS_LIB)) +# 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 -OBJS=$(OBJS_MAIN) $(OBJS_LIB) # cflags ifndef CFLAGS @@ -34,7 +35,7 @@ ifndef CFLAGS endif HASH := \# -HAS_UNWIND := $(shell echo '$(HASH)include \nvoid main() { _Unwind_Backtrace(0, 0);}' | $(CC) -x c - -o /dev/null >/dev/null 2>&1 && echo 1 || echo 0) +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 @@ -70,10 +71,17 @@ endif ifdef STATIC override CFLAGS += -DBUILD_STATIC - override LDFLAGS += -lssl -lcrypto -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -ldl -lm -static -rdynamic + override LDFLAGS += -lssl -lcrypto -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -ldl -lm -lcre2 -static -rdynamic else - override LDFLAGS += -lssl -lcrypto -lpthread -ldl -lm -rdynamic + override LDFLAGS += -lssl -lcrypto -lpthread -ldl -lm -lcre2 -rdynamic endif + +USE_ATOMIC := $(shell printf '$(HASH)include \nvoid main() { uint64_t value=0;__sync_add_and_fetch(&value, 1);}' | $(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 @@ -93,7 +101,7 @@ $(SMARTDNS_LIB): $(OBJS) $(AR) rcs $@ $^ clang-tidy: - clang-tidy -p=. $(OBJS_MAIN:.o=.c) -- $(CFLAGS) + clang-tidy -p=. $(LINT_OBJS:.o=.c) -- $(CFLAGS) clean: $(RM) $(OBJS) $(BIN) $(SMARTDNS_LIB) $(MAIN_OBJ) $(SMARTDNS_TEST_LIB) $(TEST_OBJS) diff --git a/src/dns.c b/src/dns.c old mode 100644 new mode 100755 index 286784a458..acf8492948 --- a/src/dns.c +++ b/src/dns.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,9 +17,9 @@ */ #define _GNU_SOURCE -#include "dns.h" -#include "stringutil.h" -#include "tlog.h" +#include "smartdns/dns.h" +#include "smartdns/lib/stringutil.h" +#include "smartdns/tlog.h" #include #include #include diff --git a/src/dns_cache.c b/src/dns_cache.c old mode 100644 new mode 100755 index 75594c1223..b65f9b9c3d --- a/src/dns_cache.c +++ b/src/dns_cache.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,12 +16,14 @@ * along with this program. If not, see . */ -#include "dns_cache.h" -#include "dns_stats.h" -#include "stringutil.h" -#include "timer.h" -#include "tlog.h" -#include "util.h" +#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 @@ -672,6 +674,7 @@ static int _dns_cache_read_record(int fd, uint32_t cache_number, dns_cache_read_ { 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; @@ -704,7 +707,8 @@ static int _dns_cache_read_record(int fd, uint32_t cache_number, dns_cache_read_ goto errout; } - cache_data = malloc(data_head.size + sizeof(data_head)); + data_size = data_head.size + sizeof(data_head); + cache_data = malloc(data_size); if (cache_data == NULL) { tlog(TLOG_ERROR, "malloc cache data failed %s", strerror(errno)); goto errout; @@ -722,6 +726,7 @@ static int _dns_cache_read_record(int fd, uint32_t cache_number, dns_cache_read_ 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; diff --git a/src/dns_client.c b/src/dns_client.c deleted file mode 100644 index a4839ab6f9..0000000000 --- a/src/dns_client.c +++ /dev/null @@ -1,6179 +0,0 @@ -/************************************************************************* - * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . - * - * smartdns is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * smartdns is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 "dns_client.h" -#include "atomic.h" -#include "dns.h" -#include "dns_conf.h" -#include "dns_server.h" -#include "dns_stats.h" -#include "fast_ping.h" -#include "hashtable.h" -#include "http_parse.h" -#include "list.h" -#include "proxy.h" -#include "tlog.h" -#include "util.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; -}; - -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; - socklen_t addr_len; - union { - struct sockaddr_in in; - struct sockaddr_in6 in6; - struct sockaddr addr; - }; -}; - -struct dns_conn_stream { - struct list_head list; - struct dns_server_buff send_buff; - struct dns_server_buff recv_buff; - - struct dns_query_struct *query; - struct dns_server_info *server_info; - - union { - SSL *quic_stream; - }; -}; - -/* 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 dns_conn_stream *conn_stream; - - /* 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); -}; -static int is_client_init; -static struct dns_client client; -static LIST_HEAD(pending_servers); -static pthread_mutex_t pending_server_mutex = PTHREAD_MUTEX_INITIALIZER; -static int dns_client_has_bootstrap_dns = 0; - -static void _dns_client_retry_dns_query(struct dns_query_struct *query); -static int _dns_client_send_udp(struct dns_server_info *server_info, void *packet, int len); -static void _dns_client_clear_wakeup_event(void); -static void _dns_client_do_wakeup_event(void); -static void _dns_client_server_close(struct dns_server_info *server_info); - -static ssize_t _ssl_read_ext(struct dns_server_info *server, SSL *ssl, void *buff, int num) -{ - ssize_t ret = 0; - if (server == NULL || buff == NULL || ssl == NULL) { - return SSL_ERROR_SYSCALL; - } - pthread_mutex_lock(&server->lock); - 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; - if (server == NULL || buff == NULL || ssl == NULL) { - return SSL_ERROR_SYSCALL; - } - - pthread_mutex_lock(&server->lock); -#ifdef OSSL_QUIC1_VERSION - ret = SSL_write_ex2(ssl, buff, num, flags, &written); -#else - ret = SSL_write_ex(ssl, buff, num, &written); -#endif - pthread_mutex_unlock(&server->lock); - - if (ret <= 0) { - return ret; - } - - return written; -} - -static int _ssl_shutdown(struct dns_server_info *server) -{ - int ret = 0; - if (server == NULL || server->ssl == NULL) { - return SSL_ERROR_SYSCALL; - } - - pthread_mutex_lock(&server->lock); - 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; - if (server == NULL || ssl == NULL) { - return SSL_ERROR_SYSCALL; - } - - pthread_mutex_lock(&server->lock); - 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; - if (server == NULL || server->ssl == NULL) { - return SSL_ERROR_SYSCALL; - } - - pthread_mutex_lock(&server->lock); - err = SSL_do_handshake(server->ssl); - pthread_mutex_unlock(&server->lock); - return err; -} - -static int _ssl_session_reused(struct dns_server_info *server) -{ - int err = 0; - if (server == NULL || server->ssl == NULL) { - return SSL_ERROR_SYSCALL; - } - - pthread_mutex_lock(&server->lock); - 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; - if (server == NULL || server->ssl == NULL) { - return NULL; - } - - pthread_mutex_lock(&server->lock); - ret = SSL_get1_session(server->ssl); - pthread_mutex_unlock(&server->lock); - return ret; -} - -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; -} - -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); -} - -static inline 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); -} - -static inline 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; -} - -static 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; -} - -/* get addr info */ -static 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; -} - -/* 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 */ -static 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; -} - -/* get server group by name */ -static 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 */ -static 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, "group %s not exist, use default group.", group_name); - goto use_default; - } - } - - return group; - -use_default: - return client.default_group; -} - -/* add server to group */ -static 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 = malloc(sizeof(*group_member)); - if (group_member == NULL) { - tlog(TLOG_ERROR, "malloc memory failed."); - goto errout; - } - - memset(group_member, 0, sizeof(*group_member)); - 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; -} - -static 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 = malloc(sizeof(*group)); - if (group == NULL) { - goto errout; - } - memset(group, 0, sizeof(*group)); - 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; -} - -/* add server to group */ -static 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); -} - -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; -} - -static 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 = malloc(sizeof(*group)); - if (group == NULL) { - goto errout; - } - - memset(group, 0, sizeof(*group)); - 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; -} - -static 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); - } -} - -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; -} - -static 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; -} - -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; -} - -static 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; -} - -/* add dns server information */ -static 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; - } - - server_info = malloc(sizeof(*server_info)); - if (server_info == NULL) { - goto errout; - } - - if (server_type != DNS_SERVER_UDP) { - flags->result_flag &= (~DNSSERVER_FLAG_CHECK_TTL); - } - - memset(server_info, 0, sizeof(*server_info)); - 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 = 0; - 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; - 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; -} - -static void _dns_client_close_socket_ext(struct dns_server_info *server_info, int no_del_conn_list) -{ - if (server_info->fd <= 0) { - return; - } - - if (server_info->ssl) { - /* Shutdown ssl */ - if (server_info->status == DNS_SERVER_STATUS_CONNECTED) { - _ssl_shutdown(server_info); - } - - if (server_info->type == DNS_SERVER_QUIC) { - struct dns_conn_stream *conn_stream = NULL; - struct dns_conn_stream *tmp = NULL; - - pthread_mutex_lock(&server_info->lock); - list_for_each_entry_safe(conn_stream, tmp, &server_info->conn_stream_list, list) - { - if (conn_stream->quic_stream) { - 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->list); - } - - pthread_mutex_unlock(&server_info->lock); - } - - SSL_free(server_info->ssl); - server_info->ssl = NULL; - server_info->ssl_write_len = -1; - } - - if (server_info->bio_method) { - BIO_meth_free(server_info->bio_method); - server_info->bio_method = NULL; - } - - /* remove fd from epoll */ - if (server_info->fd > 0) { - epoll_ctl(client.epoll_fd, EPOLL_CTL_DEL, server_info->fd, NULL); - } - - if (server_info->proxy) { - proxy_conn_free(server_info->proxy); - server_info->proxy = NULL; - } else { - close(server_info->fd); - } - - server_info->fd = -1; - server_info->status = DNS_SERVER_STATUS_DISCONNECTED; - /* update send recv time */ - time(&server_info->last_send); - time(&server_info->last_recv); - tlog(TLOG_DEBUG, "server %s closed.", server_info->ip); -} - -static void _dns_client_close_socket(struct dns_server_info *server_info) -{ - _dns_client_close_socket_ext(server_info, 0); -} - -static 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; - } -} - -static 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 */ -static 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; -} - -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 = malloc(sizeof(*pending)); - if (pending == NULL) { - tlog(TLOG_ERROR, "malloc failed"); - goto errout; - } - memset(pending, 0, sizeof(*pending)); - - 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; -} - -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; -} - -static 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; -} - -void dns_client_flags_init(struct client_dns_server_flags *flags) -{ - memset(flags, 0, sizeof(*flags)); -} - -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); -} - -static 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); - } -} - -static void _dns_client_conn_stream_free(struct dns_conn_stream *stream) -{ - if (stream->server_info) { - pthread_mutex_lock(&stream->server_info->lock); - list_del_init(&stream->list); - pthread_mutex_unlock(&stream->server_info->lock); - } - - if (stream->quic_stream) { - SSL_free(stream->quic_stream); - stream->quic_stream = NULL; - stream->query = NULL; - } - - free(stream); -} - -static 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; - - 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); - } - - if (query->conn_stream) { - _dns_client_conn_stream_free(query->conn_stream); - query->conn_stream = NULL; - } - - /* 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); -} - -static 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); -} - -static 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); - } -} - -static 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); -} - -static 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; - } - -#ifdef OSSL_QUIC1_VERSION - if (server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) { - if (server_info->ssl) { - SSL_handle_events(server_info->ssl); - if (SSL_get_shutdown(server_info->ssl) != 0) { - _dns_client_close_socket_ext(server_info, 1); - tlog(TLOG_DEBUG, "quick server %s shutdown.", server_info->ip); - } - } - } -#endif - - if (server_info->status == DNS_SERVER_STATUS_CONNECTING) { - if (server_info->last_recv + DNS_TCP_CONNECT_TIMEOUT < now) { - tlog(TLOG_DEBUG, "server %s connect timeout.", server_info->ip); - _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); -} - -static 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; -} - -static int _dns_replied_check_add(struct dns_query_struct *dns_query, struct sockaddr *addr, socklen_t addr_len) -{ - uint32_t key = 0; - struct dns_query_replied *replied_map = NULL; - - if (addr_len > sizeof(struct sockaddr_in6)) { - tlog(TLOG_ERROR, "addr length is invalid."); - return -1; - } - - /* avoid multiple replies from one server */ - key = jhash(addr, addr_len, 0); - hash_for_each_possible(dns_query->replied_map, replied_map, node, key) - { - /* already replied, ignore this reply */ - if (memcmp(&replied_map->addr, addr, addr_len) == 0) { - return -1; - } - } - - replied_map = malloc(sizeof(*replied_map)); - if (replied_map == NULL) { - tlog(TLOG_ERROR, "malloc failed"); - return -1; - } - - /* add address info to check hashtable */ - memcpy(&replied_map->addr, addr, addr_len); - hash_add(dns_query->replied_map, &replied_map->node, key); - return 0; -} - -static void _dns_replied_check_remove(struct dns_query_struct *dns_query, struct sockaddr *addr, socklen_t addr_len) -{ - uint32_t key = 0; - struct dns_query_replied *replied_map = NULL; - - if (addr_len > sizeof(struct sockaddr_in6)) { - return; - } - - key = jhash(addr, addr_len, 0); - hash_for_each_possible(dns_query->replied_map, replied_map, node, key) - { - if (memcmp(&replied_map->addr, addr, addr_len) == 0) { - hash_del(&replied_map->node); - free(replied_map); - return; - } - } -} - -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; -} - -static 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, from, from_len) != 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, from, from_len); - 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_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; -} - -static int _dns_client_create_socket_udp(struct dns_server_info *server_info) -{ - int fd = 0; - 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; - - 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_create_socket_udp_mdns(struct dns_server_info *server_info) -{ - int fd = 0; - 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(AF_INET, 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; - - 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; -} - -static int _dns_client_create_socket_tcp(struct dns_server_info *server_info) -{ - int fd = 0; - 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->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; - - if (fd > 0 && proxy == NULL) { - close(fd); - } - - if (proxy) { - proxy_conn_free(proxy); - } - - return -1; -} - -#ifdef OSSL_QUIC1_VERSION -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; - } - - 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; - } - - 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 - -static int _dns_client_create_socket_quic(struct dns_server_info *server_info, const char *hostname, const char *alpn) -{ -#ifdef OSSL_QUIC1_VERSION - int fd = 0; - 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; - } - - 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 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; - - if (fd > 0 && proxy == NULL) { - close(fd); - } - - if (ssl) { - SSL_free(ssl); - } - - if (proxy) { - proxy_conn_free(proxy); - } - - return -1; -#else - return -1; -#endif -} - -static int _dns_client_create_socket_tls(struct dns_server_info *server_info, const char *hostname, const char *alpn) -{ - int fd = 0; - 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 (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 (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->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]; - int32_t 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; - } - } - - 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, "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; - - if (fd > 0 && proxy == NULL) { - close(fd); - } - - if (ssl) { - SSL_free(ssl); - } - - if (proxy) { - proxy_conn_free(proxy); - } - - return -1; -} - -static int _dns_client_create_socket(struct dns_server_info *server_info) -{ - time(&server_info->last_send); - time(&server_info->last_recv); - - if (server_info->fd > 0) { - return -1; - } - - if (server_info->type == DNS_SERVER_UDP) { - return _dns_client_create_socket_udp(server_info); - } else if (server_info->type == DNS_SERVER_MDNS) { - return _dns_client_create_socket_udp_mdns(server_info); - } else if (server_info->type == DNS_SERVER_TCP) { - return _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; - return _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; - } - return _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; - flag_https = &server_info->flags.https; - return _dns_client_create_socket_tls(server_info, flag_https->hostname, flag_https->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; - } - return _dns_client_create_socket_quic(server_info, flag_https->hostname, alpn); - } else { - return -1; - } - - return 0; -} - -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(&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", server_info->ip); - 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(&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(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 < 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; -} - -static 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_ERROR, "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; -} - -static 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); - 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_ERROR, "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; -} - -static 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); -} - -static 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); -} - -static 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 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; - } -} - -static 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; - } -} - -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; - } - - 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; - } - - 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; -} - -#ifdef OSSL_QUIC1_VERSION -static 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; - } - - tlog(TLOG_DEBUG, "remote server not supported."); - 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; -} - -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, 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(&conn_stream->list); - list_add_tail(&conn_stream->list, &processed_list); - } - pthread_mutex_unlock(&server_info->lock); - - if (poll_item_count <= 0) { - SSL_handle_events(server_info->ssl); - 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"); - 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)); - goto errout; - } - - conn_stream->recv_buff.len += read_len; - - if (server_info->type == DNS_SERVER_HTTP3) { - ret = _dns_client_process_recv_http3(server_info, conn_stream); - if (ret != 0) { - goto errout; - } - - } 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 */ - goto errout; - } - - 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) { - goto errout; - } - } - } - } - } - poll_ret = 0; - goto out; -errout: - poll_ret = -1; - goto out; -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; -} - -static 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 peer close", server_info->ip); - 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, list) - { - if (conn_stream->quic_stream != NULL) { - 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_handle_events(server_info->ssl); - } - } - - 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 - -static 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", server_info->ip); - 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); - } - /* 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; -} - -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; -} - -static int _dns_client_verify_common_name(struct dns_server_info *server_info, X509 *cert, char *peer_CN) -{ - char *tls_host_verify = NULL; - GENERAL_NAMES *alt_names = NULL; - int i = 0; - - /* 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; - } - } - - alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); - if (alt_names == NULL) { - 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; - } - } - -errout: - tlog(TLOG_WARN, "server %s CN is invalid, peer CN: %s, expect CN: %s", server_info->ip, peer_CN, tls_host_verify); - server_info->prohibit = 1; - if (alt_names) { - GENERAL_NAMES_free(alt_names); - } - return -1; -} - -static int _dns_client_tls_verify(struct dns_server_info *server_info) -{ - X509 *cert = NULL; - X509_PUBKEY *pubkey = NULL; - char peer_CN[256]; - 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; - - 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); - peer_CN[0] = '\0'; - _dns_client_tls_get_cert_CN(cert, peer_CN, sizeof(peer_CN)); - tlog(TLOG_WARN, "peer server %s certificate verify failed, %s", server_info->ip, - X509_verify_cert_error_string(res)); - tlog(TLOG_WARN, "peer CN: %s", peer_CN); - goto errout; - } - } - pthread_mutex_unlock(&server_info->lock); - - 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); - - if (_dns_client_verify_common_name(server_info, cert, peer_CN) != 0) { - 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); - } - } - - OPENSSL_free(key_data); - X509_free(cert); - return 0; - -errout: - if (key_data) { - OPENSSL_free(key_data); - } - - if (cert) { - X509_free(cert); - } - - return -1; -} - -static 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 hand shake */ - 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 connected\n", server_info->ip); - /* 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(&client.server_list_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(&client.server_list_lock); - goto errout; - } - - /* save ssl session for next request */ - server_info->ssl_session = _ssl_get1_session(server_info); - pthread_mutex_unlock(&client.server_list_lock); - } - - 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 */ -#ifdef OSSL_QUIC1_VERSION - return _dns_client_process_quic(server_info, event, now); -#else - tlog(TLOG_ERROR, "quic/http3 is not supported."); - goto errout; -#endif - } - - return _dns_client_process_tcp(server_info, event, now); -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_proxy_handshake(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now) -{ - struct epoll_event fd_event; - proxy_handshake_state ret = proxy_conn_handshake(server_info->proxy); - int fd = server_info->fd; - int retval = -1; - int epoll_op = EPOLL_CTL_MOD; - - if (ret == PROXY_HANDSHAKE_OK) { - 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; - } - - return retval; - -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(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_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; -} - -static 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; -} - -static 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; -} - -static 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) { - tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); - return -1; - } - - return 0; -} - -static 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; -} - -static 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; -} - -static int _dns_client_send_https(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; - struct client_dns_server_flag_https *https_flag = NULL; - - if (len > sizeof(inpacket_data) - 2) { - tlog(TLOG_ERROR, "packet size is invalid."); - return -1; - } - - https_flag = &server_info->flags.https; - - http_len = snprintf((char *)inpacket, DNS_IN_PACKSIZE, - "POST %s HTTP/1.1\r\n" - "Host: %s\r\n" - "User-Agent: smartdns\r\n" - "Content-Type: application/dns-message\r\n" - "Content-Length: %d\r\n" - "\r\n", - https_flag->path, https_flag->httphost, len); - memcpy(inpacket + http_len, packet, len); - http_len += len; - - 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; -} - -#ifdef OSSL_QUIC1_VERSION -static int _dns_client_quic_pending_data(struct dns_conn_stream *stream, struct dns_server_info *server_info, - void *packet, int len) -{ - struct epoll_event event; - if (DNS_TCP_BUFFER - stream->send_buff.len < len) { - errno = ENOMEM; - return -1; - } - - memcpy(stream->send_buff.data + stream->send_buff.len, packet, len); - stream->send_buff.len += len; - - pthread_mutex_lock(&server_info->lock); - if (list_empty(&server_info->conn_stream_list)) { - list_add_tail(&stream->list, &server_info->conn_stream_list); - } - pthread_mutex_unlock(&server_info->lock); - - 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) { - tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); - return -1; - } - return 0; -} -#endif - -static int _dns_client_send_quic(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet, - unsigned short len) -{ -#ifdef OSSL_QUIC1_VERSION - 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; - } - - if (query->conn_stream) { - _dns_client_conn_stream_free(query->conn_stream); - query->conn_stream = NULL; - } - - /* 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); - - struct dns_conn_stream *stream = NULL; - stream = malloc(sizeof(struct dns_conn_stream)); - if (stream == NULL) { - tlog(TLOG_ERROR, "malloc memory failed."); - return -1; - } - - memset(stream, 0, sizeof(struct dns_conn_stream)); - stream->query = query; - stream->server_info = server_info; - query->conn_stream = stream; - INIT_LIST_HEAD(&stream->list); - - if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { - return _dns_client_quic_pending_data(stream, server_info, inpacket, len); - } - - /* run hand shake */ - SSL_handle_events(server_info->ssl); - - SSL *quic_stream = SSL_new_stream(server_info->ssl, 0); - if (quic_stream == NULL) { - _dns_client_shutdown_socket(server_info); - return _dns_client_quic_pending_data(stream, server_info, inpacket, len); - } - - pthread_mutex_lock(&server_info->lock); - list_add_tail(&stream->list, &server_info->conn_stream_list); - stream->quic_stream = quic_stream; - pthread_mutex_unlock(&server_info->lock); - - /* bind stream */ - SSL_set_ex_data(quic_stream, 0, stream); - - send_len = _dns_client_socket_ssl_send_ext(server_info, quic_stream, inpacket, len, SSL_WRITE_FLAG_CONCLUDE); - 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_quic_pending_data(stream, 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_quic_pending_data(stream, server_info, inpacket + send_len, len - send_len); - } -#else - tlog(TLOG_ERROR, "quic is not supported."); -#endif - return 0; -} - -static int _dns_client_send_http3(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet, - unsigned short len) -{ -#ifdef OSSL_QUIC1_VERSION - int send_len = 0; - int http_len = 0; - unsigned char inpacket_data[DNS_IN_PACKSIZE]; - unsigned char *inpacket = inpacket_data; - 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; - } - - if (query->conn_stream) { - _dns_client_conn_stream_free(query->conn_stream); - query->conn_stream = NULL; - } - - 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-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; - } - - struct dns_conn_stream *stream = NULL; - stream = malloc(sizeof(struct dns_conn_stream)); - if (stream == NULL) { - tlog(TLOG_ERROR, "malloc memory failed."); - goto errout; - } - - memset(stream, 0, sizeof(struct dns_conn_stream)); - stream->query = query; - stream->server_info = server_info; - query->conn_stream = stream; - INIT_LIST_HEAD(&stream->list); - - if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { - return _dns_client_quic_pending_data(stream, server_info, inpacket, http_len); - } - - /* run hand shake */ - SSL_handle_events(server_info->ssl); - - SSL *quic_stream = SSL_new_stream(server_info->ssl, 0); - if (quic_stream == NULL) { - _dns_client_shutdown_socket(server_info); - return _dns_client_quic_pending_data(stream, server_info, inpacket, http_len); - } - - pthread_mutex_lock(&server_info->lock); - list_add_tail(&stream->list, &server_info->conn_stream_list); - stream->quic_stream = quic_stream; - pthread_mutex_unlock(&server_info->lock); - - /* bind stream */ - SSL_set_ex_data(quic_stream, 0, stream); - - send_len = _dns_client_socket_ssl_send_ext(server_info, quic_stream, inpacket, http_len, SSL_WRITE_FLAG_CONCLUDE); - 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_quic_pending_data(stream, server_info, inpacket, http_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_quic_pending_data(stream, server_info, inpacket + send_len, http_len - send_len); - } - - http_head_destroy(http_head); - return 0; -errout: - if (http_head) { - http_head_destroy(http_head); - } - - return -1; -#else - tlog(TLOG_ERROR, "http3 is not supported."); -#endif - return 0; -} - -static 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; -} - -static 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 send_err = 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 (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); - 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)) { - continue; - } - server_info->prohibit = 0; - server_info->is_already_prohibit = 0; - _dns_server_dec_prohibit_server_num(server_info); - if (now - 60 > server_info->last_send) { - _dns_client_close_socket(server_info); - } - } - 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; - } - - atomic_inc(&query->dns_request_sent); - stats_inc(&server_info->stats.total); - send_count++; - errno = 0; - server_info->send_tick = get_tick_count(); - - 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 */ - ret = _dns_client_send_https(server_info, 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); - 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) { - if (send_err == ENETUNREACH) { - 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); - atomic_dec(&query->dns_request_sent); - send_count--; - continue; - } - - 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); - send_count--; - continue; - } - time(&server_info->last_send); - } - 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; -} - -static 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); -} - -static 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); -} - -static 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; -} - -static 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; -} - -static 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); - } -} - -static 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; -} - -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 = malloc(sizeof(*query)); - if (query == NULL) { - goto errout; - } - memset(query, 0, sizeof(*query)); - - INIT_HLIST_NODE(&query->domain_node); - INIT_LIST_HEAD(&query->dns_request_list); - 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_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); -} - -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; -} - -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; -} - -static 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); - } -} - -static 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; - } - } -} - -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 long last = {0}; - unsigned int msec = 0; - unsigned int sleep = 100; - int sleep_time = 0; - unsigned long expect_time = 0; - int unused __attribute__((unused)); - - sleep_time = sleep; - now = get_tick_count() - sleep; - last = now; - expect_time = now + sleep; - while (atomic_read(&client.run)) { - now = get_tick_count(); - if (sleep_time > 0) { - sleep_time -= now - last; - if (sleep_time <= 0) { - sleep_time = 0; - } - - int cnt = sleep_time / sleep; - msec -= cnt; - expect_time -= cnt * sleep; - sleep_time -= cnt * sleep; - } - - if (now >= expect_time) { - msec++; - if (last != now) { - _dns_client_period_run(msec); - } - - sleep_time = sleep - (now - expect_time); - if (sleep_time < 0) { - sleep_time = 0; - expect_time = now; - } - - /* 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)) { - int cnt = 10 - (msec % 10) - 1; - sleep_time += sleep * cnt; - msec += cnt; - /* sleep to next second */ - expect_time += sleep * cnt; - } - pthread_mutex_unlock(&client.domain_map_lock); - expect_time += sleep; - } - last = now; - - 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; -} - -static 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; -} - -static void _dns_client_close_wakeup_event(void) -{ - if (client.fd_wakeup > 0) { - close(client.fd_wakeup); - client.fd_wakeup = -1; - } -} - -static 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)); -} - -static 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)); -} - -static 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) { - 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)); - 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 failed."); - 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 failed."); - goto errout; - } - } - - freeifaddrs(ifaddr); - - return 0; - -errout: - if (ifaddr) { - freeifaddrs(ifaddr); - } - - return -1; -} - -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; -} diff --git a/src/dns_client/client_http3.c b/src/dns_client/client_http3.c new file mode 100755 index 0000000000..51db4d5035 --- /dev/null +++ b/src/dns_client/client_http3.c @@ -0,0 +1,141 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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) +{ +#ifdef OSSL_QUIC1_VERSION + 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-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; +} + +#ifdef OSSL_QUIC1_VERSION +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 \ No newline at end of file diff --git a/src/dns_client/client_http3.h b/src/dns_client/client_http3.h new file mode 100755 index 0000000000..6e97352739 --- /dev/null +++ b/src/dns_client/client_http3.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/client_https.c b/src/dns_client/client_https.c new file mode 100755 index 0000000000..28cd4a9636 --- /dev/null +++ b/src/dns_client/client_https.c @@ -0,0 +1,82 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_send_https(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; + struct client_dns_server_flag_https *https_flag = NULL; + + if (len > sizeof(inpacket_data) - 2) { + tlog(TLOG_ERROR, "packet size is invalid."); + return -1; + } + + https_flag = &server_info->flags.https; + + http_len = snprintf((char *)inpacket, DNS_IN_PACKSIZE, + "POST %s HTTP/1.1\r\n" + "Host: %s\r\n" + "User-Agent: smartdns\r\n" + "Content-Type: 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 >= DNS_IN_PACKSIZE) { + tlog(TLOG_ERROR, "http header size is invalid."); + return -1; + } + + memcpy(inpacket + http_len, packet, len); + http_len += len; + + 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; +} diff --git a/src/dns_client/client_https.h b/src/dns_client/client_https.h new file mode 100755 index 0000000000..22c30bed17 --- /dev/null +++ b/src/dns_client/client_https.h @@ -0,0 +1,33 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_https(struct dns_server_info *server_info, void *packet, unsigned short len); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_client/client_mdns.c b/src/dns_client/client_mdns.c new file mode 100755 index 0000000000..421dcd3d49 --- /dev/null +++ b/src/dns_client/client_mdns.c @@ -0,0 +1,208 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = 0; + 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(AF_INET, 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; + + 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) { + 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)); + 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 failed."); + 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 failed."); + goto errout; + } + } + + freeifaddrs(ifaddr); + + return 0; + +errout: + if (ifaddr) { + freeifaddrs(ifaddr); + } + + return -1; +} diff --git a/src/dns_client/client_mdns.h b/src/dns_client/client_mdns.h new file mode 100755 index 0000000000..4d7f9667e6 --- /dev/null +++ b/src/dns_client/client_mdns.h @@ -0,0 +1,37 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/client_quic.c b/src/dns_client/client_quic.c new file mode 100755 index 0000000000..68300e5676 --- /dev/null +++ b/src/dns_client/client_quic.c @@ -0,0 +1,733 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 + +#ifdef OSSL_QUIC1_VERSION +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; + } + + 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; + } + + 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) +{ +#ifdef OSSL_QUIC1_VERSION + int fd = 0; + 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 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; + + if (fd > 0 && proxy == NULL) { + close(fd); + } + + if (ssl) { + SSL_free(ssl); + } + + if (proxy) { + proxy_conn_free(proxy); + } + + return -1; +#else + return -1; +#endif +} + +#ifdef OSSL_QUIC1_VERSION +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_handle_events(server_info->ssl); + 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 peer close", server_info->ip); + 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_handle_events(server_info->ssl); + } + } + + 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 + +#ifdef OSSL_QUIC1_VERSION +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 (server_info->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 (list_empty(&stream->server_list)) { + list_add_tail(&stream->server_list, &server_info->conn_stream_list); + _dns_client_conn_stream_get(stream); + } + stream->server_info = server_info; + + if (list_empty(&stream->query_list)) { + list_add_tail(&stream->query_list, &query->conn_stream_list); + _dns_client_conn_stream_get(stream); + } + stream->query = query; + pthread_mutex_unlock(&server_info->lock); + + if (client.epoll_fd <= 0) { + errno = ECONNRESET; + goto errout; + } + + 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)); + goto errout; + } + return 0; +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; + } + + if (server_info->status != DNS_SERVER_STATUS_CONNECTED) { + ret = _dns_client_quic_pending_data(stream, server_info, query, packet, len); + goto out; + } + + /* run hand shake */ + SSL_handle_events(server_info->ssl); + + 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); + list_add_tail(&stream->server_list, &server_info->conn_stream_list); + _dns_client_conn_stream_get(stream); + stream->server_info = server_info; + + list_add_tail(&stream->query_list, &query->conn_stream_list); + _dns_client_conn_stream_get(stream); + stream->query = query; + 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 || errno == EPIPE || 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) +{ +#ifdef OSSL_QUIC1_VERSION + 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; +} diff --git a/src/dns_client/client_quic.h b/src/dns_client/client_quic.h new file mode 100755 index 0000000000..0c94c8162e --- /dev/null +++ b/src/dns_client/client_quic.h @@ -0,0 +1,42 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/client_socket.c b/src/dns_client/client_socket.c new file mode 100755 index 0000000000..dd1afed874 --- /dev/null +++ b/src/dns_client/client_socket.c @@ -0,0 +1,288 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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) +{ + time(&server_info->last_send); + time(&server_info->last_recv); + + if (server_info->fd > 0) { + return -1; + } + + if (server_info->type == DNS_SERVER_UDP) { + return _dns_client_create_socket_udp(server_info); + } else if (server_info->type == DNS_SERVER_MDNS) { + return _dns_client_create_socket_udp_mdns(server_info); + } else if (server_info->type == DNS_SERVER_TCP) { + return _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; + return _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; + } + return _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; + flag_https = &server_info->flags.https; + return _dns_client_create_socket_tls(server_info, flag_https->hostname, flag_https->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; + } + return _dns_client_create_socket_quic(server_info, flag_https->hostname, alpn); + } else { + return -1; + } + + return 0; +} + +void _dns_client_close_socket_ext(struct dns_server_info *server_info, int no_del_conn_list) +{ + if (server_info->fd <= 0) { + return; + } + + if (server_info->ssl) { + /* Shutdown ssl */ + if (server_info->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; + + 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) { +#ifdef OSSL_QUIC1_VERSION + 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); + } + + pthread_mutex_unlock(&server_info->lock); + } + + SSL_free(server_info->ssl); + server_info->ssl = NULL; + server_info->ssl_write_len = -1; + } + + if (server_info->bio_method) { + BIO_meth_free(server_info->bio_method); + server_info->bio_method = NULL; + } + + /* remove fd from epoll */ + if (server_info->fd > 0) { + epoll_ctl(client.epoll_fd, EPOLL_CTL_DEL, server_info->fd, NULL); + } + + if (server_info->proxy) { + proxy_conn_free(server_info->proxy); + server_info->proxy = NULL; + } else { + close(server_info->fd); + } + + server_info->fd = -1; + server_info->status = DNS_SERVER_STATUS_DISCONNECTED; + /* update send recv time */ + time(&server_info->last_send); + time(&server_info->last_recv); + tlog(TLOG_DEBUG, "server %s:%d closed.", server_info->ip, server_info->port); +} + +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; +} diff --git a/src/dns_client/client_socket.h b/src/dns_client/client_socket.h new file mode 100755 index 0000000000..b36c0d3ba8 --- /dev/null +++ b/src/dns_client/client_socket.h @@ -0,0 +1,47 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/client_tcp.c b/src/dns_client/client_tcp.c new file mode 100755 index 0000000000..6925d86795 --- /dev/null +++ b/src/dns_client/client_tcp.c @@ -0,0 +1,450 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_socket.h" +#include "client_tcp.h" +#include "server_info.h" + +#include +#include +#include +#include +#include + +int _dns_client_create_socket_tcp(struct dns_server_info *server_info) +{ + int fd = 0; + 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->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; + + 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; +} + +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", server_info->ip); + 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); + } + /* 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; + } + +#ifdef OSSL_QUIC1_VERSION + if (server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) { + if (server_info->ssl) { + SSL_handle_events(server_info->ssl); + if (SSL_get_shutdown(server_info->ssl) != 0) { + _dns_client_close_socket_ext(server_info, 1); + tlog(TLOG_DEBUG, "quick server %s shutdown.", server_info->ip); + } + } + } +#endif + + if (server_info->status == DNS_SERVER_STATUS_CONNECTING) { + if (server_info->last_recv + DNS_TCP_CONNECT_TIMEOUT < now) { + tlog(TLOG_DEBUG, "server %s connect timeout.", server_info->ip); + _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); +} diff --git a/src/dns_client/client_tcp.h b/src/dns_client/client_tcp.h new file mode 100755 index 0000000000..c56195a491 --- /dev/null +++ b/src/dns_client/client_tcp.h @@ -0,0 +1,41 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/client_tls.c b/src/dns_client/client_tls.c new file mode 100755 index 0000000000..95d0cbc173 --- /dev/null +++ b/src/dns_client/client_tls.c @@ -0,0 +1,1089 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_quic.h" +#include "client_socket.h" +#include "client_tcp.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; + if (server == NULL || buff == NULL || ssl == NULL) { + return SSL_ERROR_SYSCALL; + } + pthread_mutex_lock(&server->lock); + 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; + if (server == NULL || buff == NULL || ssl == NULL) { + return SSL_ERROR_SYSCALL; + } + + pthread_mutex_lock(&server->lock); +#ifdef OSSL_QUIC1_VERSION + ret = SSL_write_ex2(ssl, buff, num, flags, &written); +#else + ret = SSL_write_ex(ssl, buff, num, &written); +#endif + pthread_mutex_unlock(&server->lock); + + if (ret <= 0) { + return ret; + } + + return written; +} + +int _ssl_shutdown(struct dns_server_info *server) +{ + int ret = 0; + if (server == NULL || server->ssl == NULL) { + return SSL_ERROR_SYSCALL; + } + + pthread_mutex_lock(&server->lock); + 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; + if (server == NULL || ssl == NULL) { + return SSL_ERROR_SYSCALL; + } + + pthread_mutex_lock(&server->lock); + 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; + if (server == NULL || server->ssl == NULL) { + return SSL_ERROR_SYSCALL; + } + + pthread_mutex_lock(&server->lock); + err = SSL_do_handshake(server->ssl); + pthread_mutex_unlock(&server->lock); + return err; +} + +static int _ssl_session_reused(struct dns_server_info *server) +{ + int err = 0; + if (server == NULL || server->ssl == NULL) { + return SSL_ERROR_SYSCALL; + } + + pthread_mutex_lock(&server->lock); + 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; + if (server == NULL || server->ssl == NULL) { + return NULL; + } + + pthread_mutex_lock(&server->lock); + 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 = 0; + 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 (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 (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->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]; + int32_t 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, "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; + + 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_ERROR, "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; + } + + tlog(TLOG_ERROR, "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; + + 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; + } + } + 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); + } + } + + OPENSSL_free(key_data); + X509_free(cert); + return 0; + +errout: + if (key_data) { + OPENSSL_free(key_data); + } + + if (cert) { + X509_free(cert); + } + + 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 hand shake */ + 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); + } + + 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 */ +#ifdef OSSL_QUIC1_VERSION + return _dns_client_process_quic(server_info, event, now); +#else + tlog(TLOG_ERROR, "quic/http3 is not supported."); + goto errout; +#endif + } + + 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; + _dns_client_close_socket(server_info); + pthread_mutex_unlock(&server_info->lock); + + 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; +} \ No newline at end of file diff --git a/src/dns_client/client_tls.h b/src/dns_client/client_tls.h new file mode 100755 index 0000000000..af10ccfc80 --- /dev/null +++ b/src/dns_client/client_tls.h @@ -0,0 +1,53 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_client/client_udp.c b/src/dns_client/client_udp.c new file mode 100755 index 0000000000..07af98226a --- /dev/null +++ b/src/dns_client/client_udp.c @@ -0,0 +1,482 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = 0; + 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; + + 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", server_info->ip); + 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); +} diff --git a/src/dns_client/client_udp.h b/src/dns_client/client_udp.h new file mode 100755 index 0000000000..78c04e776d --- /dev/null +++ b/src/dns_client/client_udp.h @@ -0,0 +1,41 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/conn_stream.c b/src/dns_client/conn_stream.c new file mode 100755 index 0000000000..730d691cda --- /dev/null +++ b/src/dns_client/conn_stream.c @@ -0,0 +1,107 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(sizeof(*stream)); + if (stream == NULL) { + tlog(TLOG_ERROR, "malloc conn stream failed"); + return NULL; + } + + memset(stream, 0, sizeof(*stream)); + INIT_LIST_HEAD(&stream->server_list); + INIT_LIST_HEAD(&stream->query_list); + stream->quic_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 refcnt is %d", refcnt); + } + return; + } + + if (stream->quic_stream) { + SSL_free(stream->quic_stream); + stream->quic_stream = NULL; + } + + if (stream->query) { + list_del_init(&stream->query_list); + stream->query = NULL; + } + + if (stream->server_info) { + pthread_mutex_lock(&stream->server_info->lock); + if (!list_empty(&stream->server_list)) { + 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) { +#ifdef OSSL_QUIC1_VERSION + SSL_stream_reset(stream->quic_stream, NULL, 0); +#endif + SSL_free(stream->quic_stream); + stream->quic_stream = NULL; + } + _dns_client_conn_stream_put(stream); + } + pthread_mutex_unlock(&server_info->lock); +} \ No newline at end of file diff --git a/src/dns_client/conn_stream.h b/src/dns_client/conn_stream.h new file mode 100755 index 0000000000..0cee8e85ea --- /dev/null +++ b/src/dns_client/conn_stream.h @@ -0,0 +1,39 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/dns_client.c b/src/dns_client/dns_client.c new file mode 100755 index 0000000000..da45cdbc66 --- /dev/null +++ b/src/dns_client/dns_client.c @@ -0,0 +1,758 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_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 "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, from, from_len) != 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, from, from_len); + 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; +} + +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 send_err = 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 (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); + 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)) { + continue; + } + server_info->prohibit = 0; + server_info->is_already_prohibit = 0; + _dns_server_dec_prohibit_server_num(server_info); + if (now - 60 > server_info->last_send) { + _dns_client_close_socket(server_info); + } + } + 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; + } + + atomic_inc(&query->dns_request_sent); + stats_inc(&server_info->stats.total); + send_count++; + errno = 0; + server_info->send_tick = get_tick_count(); + + 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 */ + ret = _dns_client_send_https(server_info, 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); + 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 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); + atomic_dec(&query->dns_request_sent); + send_count--; + continue; + } + 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); + send_count--; + continue; + } + time(&server_info->last_send); + } + 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 = malloc(sizeof(*query)); + if (query == NULL) { + goto errout; + } + memset(query, 0, sizeof(*query)); + + INIT_HLIST_NODE(&query->domain_node); + INIT_LIST_HEAD(&query->dns_request_list); + INIT_LIST_HEAD(&query->conn_stream_list); + 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 long last = {0}; + unsigned int msec = 0; + unsigned int sleep = 100; + int sleep_time = 0; + unsigned long expect_time = 0; + int unused __attribute__((unused)); + + sleep_time = sleep; + now = get_tick_count() - sleep; + last = now; + expect_time = now + sleep; + while (atomic_read(&client.run)) { + now = get_tick_count(); + if (sleep_time > 0) { + sleep_time -= now - last; + if (sleep_time <= 0) { + sleep_time = 0; + } + + int cnt = sleep_time / sleep; + msec -= cnt; + expect_time -= cnt * sleep; + sleep_time -= cnt * sleep; + } + + if (now >= expect_time) { + msec++; + if (last != now) { + _dns_client_period_run(msec); + } + + sleep_time = sleep - (now - expect_time); + if (sleep_time < 0) { + sleep_time = 0; + expect_time = now; + } + + /* 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)) { + int cnt = 10 - (msec % 10) - 1; + sleep_time += sleep * cnt; + msec += cnt; + /* sleep to next second */ + expect_time += sleep * cnt; + } + pthread_mutex_unlock(&client.domain_map_lock); + expect_time += sleep; + } + last = now; + + 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; +} diff --git a/src/dns_client/dns_client.h b/src/dns_client/dns_client.h new file mode 100755 index 0000000000..619438ecdf --- /dev/null +++ b/src/dns_client/dns_client.h @@ -0,0 +1,290 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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/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; +}; + +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; + socklen_t addr_len; + union { + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr addr; + }; +}; + +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; + + union { + SSL *quic_stream; + }; +}; + +/* 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); +}; + +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 diff --git a/src/dns_client/ecs.c b/src/dns_client/ecs.c new file mode 100755 index 0000000000..386c21487a --- /dev/null +++ b/src/dns_client/ecs.c @@ -0,0 +1,119 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/dns_client/ecs.h b/src/dns_client/ecs.h new file mode 100755 index 0000000000..beea5a3045 --- /dev/null +++ b/src/dns_client/ecs.h @@ -0,0 +1,37 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/group.c b/src/dns_client/group.c new file mode 100755 index 0000000000..674ec8fc26 --- /dev/null +++ b/src/dns_client/group.c @@ -0,0 +1,263 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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, "group %s not exist, use default 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 = malloc(sizeof(*group_member)); + if (group_member == NULL) { + tlog(TLOG_ERROR, "malloc memory failed."); + goto errout; + } + + memset(group_member, 0, sizeof(*group_member)); + 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 = malloc(sizeof(*group)); + if (group == NULL) { + goto errout; + } + + memset(group, 0, sizeof(*group)); + 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); + } +} \ No newline at end of file diff --git a/src/dns_client/group.h b/src/dns_client/group.h new file mode 100755 index 0000000000..f8a6e5617f --- /dev/null +++ b/src/dns_client/group.h @@ -0,0 +1,41 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/packet.c b/src/dns_client/packet.c new file mode 100755 index 0000000000..e36de14bbe --- /dev/null +++ b/src/dns_client/packet.c @@ -0,0 +1,115 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/dns_client/packet.h b/src/dns_client/packet.h new file mode 100755 index 0000000000..f166825323 --- /dev/null +++ b/src/dns_client/packet.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/pending_server.c b/src/dns_client/pending_server.c new file mode 100755 index 0000000000..498c49416b --- /dev/null +++ b/src/dns_client/pending_server.c @@ -0,0 +1,499 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(sizeof(*group)); + if (group == NULL) { + goto errout; + } + memset(group, 0, sizeof(*group)); + 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 = malloc(sizeof(*pending)); + if (pending == NULL) { + tlog(TLOG_ERROR, "malloc failed"); + goto errout; + } + memset(pending, 0, sizeof(*pending)); + + 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; + } + } +} diff --git a/src/dns_client/pending_server.h b/src/dns_client/pending_server.h new file mode 100755 index 0000000000..fb41bde5a3 --- /dev/null +++ b/src/dns_client/pending_server.h @@ -0,0 +1,48 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/proxy.c b/src/dns_client/proxy.c new file mode 100755 index 0000000000..300a99167f --- /dev/null +++ b/src/dns_client/proxy.c @@ -0,0 +1,104 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = proxy_conn_handshake(server_info->proxy); + int fd = server_info->fd; + int retval = -1; + int epoll_op = EPOLL_CTL_MOD; + + if (ret == PROXY_HANDSHAKE_OK) { + 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; + } + + return retval; + +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; +} diff --git a/src/dns_client/proxy.h b/src/dns_client/proxy.h new file mode 100755 index 0000000000..f739df0530 --- /dev/null +++ b/src/dns_client/proxy.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/query.c b/src/dns_client/query.c new file mode 100755 index 0000000000..23f70cf1b6 --- /dev/null +++ b/src/dns_client/query.c @@ -0,0 +1,378 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); + } + + 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); + } + + /* 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 sockaddr *addr, socklen_t addr_len) +{ + uint32_t key = 0; + struct dns_query_replied *replied_map = NULL; + + if (addr_len > sizeof(struct sockaddr_in6)) { + tlog(TLOG_ERROR, "addr length is invalid."); + return -1; + } + + /* avoid multiple replies from one server */ + key = jhash(addr, addr_len, 0); + hash_for_each_possible(dns_query->replied_map, replied_map, node, key) + { + /* already replied, ignore this reply */ + if (memcmp(&replied_map->addr, addr, addr_len) == 0) { + return -1; + } + } + + replied_map = malloc(sizeof(*replied_map)); + if (replied_map == NULL) { + tlog(TLOG_ERROR, "malloc failed"); + return -1; + } + + /* add address info to check hashtable */ + memcpy(&replied_map->addr, addr, addr_len); + hash_add(dns_query->replied_map, &replied_map->node, key); + return 0; +} + +void _dns_replied_check_remove(struct dns_query_struct *dns_query, struct sockaddr *addr, socklen_t addr_len) +{ + uint32_t key = 0; + struct dns_query_replied *replied_map = NULL; + + if (addr_len > sizeof(struct sockaddr_in6)) { + return; + } + + key = jhash(addr, addr_len, 0); + hash_for_each_possible(dns_query->replied_map, replied_map, node, key) + { + if (memcmp(&replied_map->addr, addr, addr_len) == 0) { + 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; +} diff --git a/src/dns_client/query.h b/src/dns_client/query.h new file mode 100755 index 0000000000..09674f7b32 --- /dev/null +++ b/src/dns_client/query.h @@ -0,0 +1,55 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 sockaddr *addr, socklen_t addr_len); + +void _dns_replied_check_remove(struct dns_query_struct *dns_query, struct sockaddr *addr, socklen_t addr_len); + +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 diff --git a/src/dns_client/server_info.c b/src/dns_client/server_info.c new file mode 100755 index 0000000000..f2f2b6d85f --- /dev/null +++ b/src/dns_client/server_info.c @@ -0,0 +1,687 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_tls.h" +#include "conn_stream.h" +#include "ecs.h" +#include "group.h" +#include "pending_server.h" +#include "client_socket.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; +} + +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; + } + + server_info = malloc(sizeof(*server_info)); + if (server_info == NULL) { + goto errout; + } + + if (server_type != DNS_SERVER_UDP) { + flags->result_flag &= (~DNSSERVER_FLAG_CHECK_TTL); + } + + memset(server_info, 0, sizeof(*server_info)); + 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 = 0; + 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; + 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); +} diff --git a/src/dns_client/server_info.h b/src/dns_client/server_info.h new file mode 100755 index 0000000000..df94b62d18 --- /dev/null +++ b/src/dns_client/server_info.h @@ -0,0 +1,48 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_client/wake_event.c b/src/dns_client/wake_event.c new file mode 100755 index 0000000000..ac33dbdbfc --- /dev/null +++ b/src/dns_client/wake_event.c @@ -0,0 +1,82 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/dns_client/wake_event.h b/src/dns_client/wake_event.h new file mode 100755 index 0000000000..cc2195d813 --- /dev/null +++ b/src/dns_client/wake_event.h @@ -0,0 +1,39 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf.c b/src/dns_conf.c deleted file mode 100644 index b8849b113c..0000000000 --- a/src/dns_conf.c +++ /dev/null @@ -1,6644 +0,0 @@ -/************************************************************************* - * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . - * - * smartdns is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * smartdns is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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.h" -#include "idna.h" -#include "list.h" -#include "rbtree.h" -#include "tlog.h" -#include "util.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TMP_BUFF_LEN 1024 - -/* ipset */ -struct dns_ipset_table { - DECLARE_HASHTABLE(ipset, 8); -}; -static struct dns_ipset_table dns_ipset_table; - -struct dns_nftset_table { - DECLARE_HASHTABLE(nftset, 8); -}; -static struct dns_nftset_table dns_nftset_table; -struct dns_domain_set_name_table dns_domain_set_name_table; -struct dns_ip_set_name_table dns_ip_set_name_table; - -/* dns groups */ -struct dns_group_table dns_group_table; -struct dns_proxy_table dns_proxy_table; -struct dns_ptr_table dns_ptr_table; - -static char dns_conf_dnsmasq_lease_file[DNS_MAX_PATH]; -static time_t dns_conf_dnsmasq_lease_file_time; - -struct dns_hosts_table dns_hosts_table; -int dns_hosts_record_num; - -/* SRV-HOST */ -struct dns_srv_record_table dns_conf_srv_record_table; - -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}}; - -char dns_conf_exist_bootstrap_dns; - -int dns_conf_has_icmp_check; -int dns_conf_has_tcp_check; -struct dns_domain_check_orders dns_conf_default_check_orders = { - .orders = - { - {.type = DOMAIN_CHECK_ICMP, .tcp_port = 0}, - {.type = DOMAIN_CHECK_TCP, .tcp_port = 80}, - {.type = DOMAIN_CHECK_TCP, .tcp_port = 443}, - }, -}; - -static int dns_has_cap_ping = 0; -int dns_ping_cap_force_enable = 0; - -struct dns_conf_group_info { - struct list_head list; - const char *group_name; - struct dns_conf_group *rule; -}; -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; -struct hash_table conf_file_table; -struct conf_file_path { - struct hlist_node node; - char file[DNS_MAX_PATH]; -}; -struct dns_conf_plugin_table dns_conf_plugin_table; - -struct dns_config dns_conf; - -static int _conf_domain_rule_nameserver(const char *domain, const char *group_name); -static int _conf_domain_rule_group(const char *domain, const char *group_name); -static int _conf_ptr_add(const char *hostname, const char *ip, int is_dynamic); -static int _conf_client_subnet(char *subnet, struct dns_edns_client_subnet *ipv4_ecs, - struct dns_edns_client_subnet *ipv6_ecs); -static int _conf_domain_rule_address(char *domain, const char *domain_address); -static struct dns_domain_rule *_config_domain_rule_get(const char *domain); -typedef int (*set_rule_add_func)(const char *value, void *priv); -static int _config_ip_rule_set_each(const char *ip_set, set_rule_add_func callback, void *priv); -static struct dns_conf_group *_config_rule_group_get(const char *group_name); -static struct dns_conf_group *_config_rule_group_new(const char *group_name); -static struct dns_conf_group *_config_current_rule_group(void); -static void _config_ip_iter_free(radix_node_t *node, void *cbctx); -static int _config_nftset_setvalue(struct dns_nftset_names *nftsets, const char *nftsetvalue); -static int _config_client_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear); -static int _config_client_rule_group_add(const char *client, const char *group_name); -static void _config_plugin_table_conf_destroy(void); - -#define group_member(m) ((void *)offsetof(struct dns_conf_group, m)) - -static __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; -} - -static __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; -} - -static __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; -} - -static __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; -} - -static __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; -} - -static __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; -} - -static __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; -} - -static 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; - } - - switch (domain_rule) { - case DOMAIN_RULE_FLAGS: - size = sizeof(struct dns_rule_flags); - break; - case DOMAIN_RULE_ADDRESS_IPV4: - size = sizeof(struct dns_rule_address_IPV4); - break; - case DOMAIN_RULE_ADDRESS_IPV6: - size = sizeof(struct dns_rule_address_IPV6); - break; - case DOMAIN_RULE_IPSET: - case DOMAIN_RULE_IPSET_IPV4: - case DOMAIN_RULE_IPSET_IPV6: - size = sizeof(struct dns_ipset_rule); - break; - case DOMAIN_RULE_NFTSET_IP: - case DOMAIN_RULE_NFTSET_IP6: - size = sizeof(struct dns_nftset_rule); - break; - case DOMAIN_RULE_NAMESERVER: - size = sizeof(struct dns_nameserver_rule); - break; - case DOMAIN_RULE_GROUP: - size = sizeof(struct dns_group_rule); - break; - case DOMAIN_RULE_CHECKSPEED: - size = sizeof(struct dns_domain_check_orders); - break; - case DOMAIN_RULE_RESPONSE_MODE: - size = sizeof(struct dns_response_mode_rule); - break; - case DOMAIN_RULE_CNAME: - size = sizeof(struct dns_cname_rule); - break; - case DOMAIN_RULE_HTTPS: - size = sizeof(struct dns_https_record_rule); - break; - case DOMAIN_RULE_TTL: - size = sizeof(struct dns_ttl_rule); - break; - default: - return NULL; - } - - size += ext_size; - rule = malloc(size); - if (!rule) { - return NULL; - } - memset(rule, 0, size); - rule->rule = domain_rule; - atomic_set(&rule->refcnt, 1); - return rule; -} - -static void *_new_dns_rule(enum domain_rule domain_rule) -{ - return _new_dns_rule_ext(domain_rule, 0); -} - -static void _dns_rule_get(struct dns_rule *rule) -{ - atomic_inc(&rule->refcnt); -} - -static void _dns_rule_put(struct dns_rule *rule) -{ - if (atomic_dec_and_test(&rule->refcnt)) { - free(rule); - } -} - -static void _dns_iplist_ip_address_add(struct dns_iplist_ip_addresses *iplist, unsigned char addr[], int addr_len) -{ - iplist->ipaddr = realloc(iplist->ipaddr, (iplist->ipaddr_num + 1) * sizeof(struct dns_iplist_ip_address)); - if (iplist->ipaddr == NULL) { - return; - } - 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++; -} - -static 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; -} - -/* create and get dns server group */ -static 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 = malloc(sizeof(*group)); - if (group == NULL) { - goto errout; - } - - memset(group, 0, sizeof(*group)); - 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; -} - -static 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) { - return 0; - } - } - - if (group->server_num >= DNS_MAX_SERVERS) { - return -1; - } - - group->servers[group->server_num] = server; - group->server_num++; - - return 0; -} - -static 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; -} - -static 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); - } -} - -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; -} - -static 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; -} - -static struct dns_conf_group_info *_config_current_group(void) -{ - return dns_conf_current_group_info; -} - -static 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_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; - - soa_table = malloc(soa_talbe_size); - if (soa_table == NULL) { - tlog(TLOG_WARN, "malloc qtype soa table failed."); - return -1; - } - group_rule->soa_table = soa_table; - - if (_config_current_rule_group() != NULL) { - /* copy parent group data. */ - memcpy(&group_rule->copy_data_section_begin, &_config_current_rule_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, _config_current_rule_group()->soa_table, soa_talbe_size); - return 0; - } - - memset(soa_table, 0, soa_talbe_size); - 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; - group_rule->dns_serve_expired_ttl = 24 * 3600 * 3; - 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; -} - -static int _config_current_group_push(const char *group_name) -{ - struct dns_conf_group_info *group_info = NULL; - struct dns_conf_group *group_rule = NULL; - - group_info = malloc(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; - } - } - - memset(group_info, 0, sizeof(*group_info)); - 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; -} - -static int _config_group_begin(void *data, int argc, char *argv[]) -{ - const char *group_name = NULL; - if (argc < 2) { - return -1; - } - - group_name = argv[1]; - if (group_name[0] == '\0') { - group_name = NULL; - } - - if (_config_current_group_push(group_name) != 0) { - return -1; - } - - return 0; -} - -static int _config_current_group_push_default(void) -{ - return _config_current_group_push(NULL); -} - -static 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; -} - -static 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; -} - -static int _config_group_end(void *data, int argc, char *argv[]) -{ - _config_current_group_pop(); - return 0; -} - -static 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 = dns_conf_current_group_info; - 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; - } - - dns_conf_current_group_info = dns_conf_default_group_info; - - 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; - } - - dns_conf_current_group_info = saved_group_info; - - return 0; -errout: - dns_conf_current_group_info = saved_group_info; - return -1; -} - -/* 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 = malloc(sizeof(*proxy)); - if (proxy == NULL) { - goto errout; - } - - memset(proxy, 0, sizeof(*proxy)); - 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; -} - -static 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; -} - -static 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; -} - -static 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); - } -} - -static void _config_srv_record_table_destroy(void) -{ - struct dns_srv_records *srv_records = NULL; - struct dns_srv_record *srv_record, *tmp1 = NULL; - struct hlist_node *tmp = NULL; - unsigned int i; - - hash_for_each_safe(dns_conf_srv_record_table.srv, i, tmp, srv_records, node) - { - list_for_each_entry_safe(srv_record, tmp1, &srv_records->list, list) - { - list_del(&srv_record->list); - free(srv_record); - } - - hlist_del_init(&srv_records->node); - free(srv_records); - } -} - -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': { - group_name = optarg; - 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; -} - -static 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; -} - -static int _config_domain_rule_free(struct dns_domain_rule *domain_rule) -{ - int i = 0; - - if (domain_rule == NULL) { - return 0; - } - - for (i = 0; i < DOMAIN_RULE_MAX; 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; -} - -static 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 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; -} - -static 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 = malloc(sizeof(*rule_group)); - if (rule_group == NULL) { - return NULL; - } - - memset(rule_group, 0, sizeof(*rule_group)); - 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); -} - -static 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; -} - -static 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)) { - line_no++; - filed_num = sscanf(line, "%255s", value); - if (filed_num <= 0) { - continue; - } - - if (value[0] == '#' || value[0] == '\n') { - continue; - } - - ret = callback(value, priv); - if (ret != 0) { - tlog(TLOG_WARN, "process file %s failed at line %d.", file, line_no); - continue; - } - } - - fclose(fp); - return ret; -} - -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(const char *domain, enum domain_rule type, void *rule); -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 (len >= domain_key_max_len - 3) { - tlog(TLOG_ERROR, "domain %s too long", domain); - 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 */ - domain_key[len + 1] = '.'; - len++; - } - - domain_key[len + 1] = 0; - domain_key[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]; - 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); -} - -static 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]; - 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 or create domain rule */ - domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); - if (domain_rule == NULL) { - add_domain_rule = malloc(sizeof(*add_domain_rule)); - if (add_domain_rule == NULL) { - goto errout; - } - memset(add_domain_rule, 0, sizeof(*add_domain_rule)); - domain_rule = add_domain_rule; - } - - /* add new rule to domain */ - if (domain_rule->rules[type]) { - _dns_rule_put(domain_rule->rules[type]); - domain_rule->rules[type] = NULL; - } - - domain_rule->rules[type] = rule; - domain_rule->sub_rule_only = sub_rule_only; - domain_rule->root_rule_only = root_rule_only; - _dns_rule_get(rule); - - /* 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 -1; -} - -static int _config_domain_rule_delete(const char *domain); -static int _config_domain_rule_delete_callback(const char *domain, void *priv) -{ - return _config_domain_rule_delete(domain); -} - -static int _config_domain_rule_delete(const char *domain) -{ - char domain_key[DNS_MAX_CONF_CNAME_LEN]; - 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_set(const char *domain, unsigned int flag, unsigned int is_clear); -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); -} - -static 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]; - 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 or create domain rule */ - domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); - if (domain_rule == NULL) { - add_domain_rule = malloc(sizeof(*add_domain_rule)); - if (add_domain_rule == NULL) { - goto errout; - } - memset(add_domain_rule, 0, sizeof(*add_domain_rule)); - domain_rule = add_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; - } - - domain_rule->sub_rule_only = sub_rule_only; - domain_rule->root_rule_only = root_rule_only; - - rule_flags = (struct dns_rule_flags *)domain_rule->rules[DOMAIN_RULE_FLAGS]; - 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; -} - -static 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); - } -} - -static 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 = malloc(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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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); - } -} - -static 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 = malloc(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; -} - -static 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(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; -} - -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; -} - -static 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; -} - -static 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; - ptr = strstr(ptr, ","); - - if (field == NULL || *field == '\0') { - break; - } - - 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 (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr) && ipv4_num < DNS_MAX_REPLY_IP_NUM) { - memcpy(ipv4_addr[ipv4_num], addr_in6->sin6_addr.s6_addr + 12, DNS_RR_A_LEN); - ipv4_num++; - } else 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -struct dns_srv_records *dns_server_get_srv_record(const char *domain) -{ - uint32_t key = 0; - - key = hash_string(domain); - struct dns_srv_records *srv_records = NULL; - hash_for_each_possible(dns_conf_srv_record_table.srv, srv_records, node, key) - { - if (strncmp(srv_records->domain, domain, DNS_MAX_CONF_CNAME_LEN) == 0) { - return srv_records; - } - } - - return NULL; -} - -static int _confg_srv_record_add(const char *domain, const char *host, unsigned short priority, unsigned short weight, - unsigned short port) -{ - struct dns_srv_records *srv_records = NULL; - struct dns_srv_record *srv_record = NULL; - uint32_t key = 0; - - srv_records = dns_server_get_srv_record(domain); - if (srv_records == NULL) { - srv_records = malloc(sizeof(*srv_records)); - if (srv_records == NULL) { - goto errout; - } - memset(srv_records, 0, sizeof(*srv_records)); - safe_strncpy(srv_records->domain, domain, DNS_MAX_CONF_CNAME_LEN); - INIT_LIST_HEAD(&srv_records->list); - key = hash_string(domain); - hash_add(dns_conf_srv_record_table.srv, &srv_records->node, key); - } - - srv_record = malloc(sizeof(*srv_record)); - if (srv_record == NULL) { - goto errout; - } - memset(srv_record, 0, sizeof(*srv_record)); - 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_records->list); - - return 0; -errout: - if (srv_record != NULL) { - free(srv_record); - } - return -1; -} - -static 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; -} - -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; -} - -static int _conf_domain_rule_https_record(const char *domain, const char *host) -{ - struct dns_https_record_rule *https_record_rule = 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; - - safe_strncpy(buff, host, sizeof(buff)); - - https_record_rule = _new_dns_rule(type); - if (https_record_rule == 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; - } - } else if (strncmp(key, "target", sizeof("target")) == 0) { - safe_strncpy(https_record_rule->record.target, val, DNS_MAX_CONF_CNAME_LEN); - https_record_rule->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 { - mode_type = 1; - https_record_rule->record.enable = 1; - if (strncmp(key, "priority", sizeof("priority")) == 0) { - priority = atoi(val); - } else if (strncmp(key, "port", sizeof("port")) == 0) { - https_record_rule->record.port = atoi(val); - - } else if (strncmp(key, "alpn", sizeof("alpn")) == 0) { - int alpn_len = _conf_domain_rule_https_copy_alpn(https_record_rule->record.alpn, DNS_MAX_ALPN_LEN, val); - if (alpn_len <= 0) { - tlog(TLOG_ERROR, "invalid option value for %s.", key); - goto errout; - } - https_record_rule->record.alpn_len = alpn_len; - } else if (strncmp(key, "ech", sizeof("ech")) == 0) { - int ech_len = SSL_base64_decode(val, https_record_rule->record.ech, DNS_MAX_ECH_LEN); - if (ech_len < 0) { - tlog(TLOG_ERROR, "invalid option value for %s.", key); - goto errout; - } - https_record_rule->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, https_record_rule->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; - } - https_record_rule->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, https_record_rule->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; - } - https_record_rule->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; - } - } - - https_record_rule->record.priority = priority; - - 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 (https_record_rule) { - _dns_rule_put(&https_record_rule->head); - } - - return -1; -} - -static 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; -} - -static void _config_speed_check_mode_clear(struct dns_domain_check_orders *check_orders) -{ - memset(check_orders->orders, 0, sizeof(check_orders->orders)); -} - -static 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; - ptr = strstr(ptr, ","); - if (field == NULL || order >= DOMAIN_CHECK_NUM) { - return 0; - } - - 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") == 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; -} - -static 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); -} - -static 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 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}, - {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; - } - 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; -} - -static int _config_bind_ip_udp(void *data, int argc, char *argv[]) -{ - return _config_bind_ip(argc, argv, DNS_BIND_TYPE_UDP); -} - -static int _config_bind_ip_tcp(void *data, int argc, char *argv[]) -{ - return _config_bind_ip(argc, argv, DNS_BIND_TYPE_TCP); -} - -static int _config_bind_ip_tls(void *data, int argc, char *argv[]) -{ - return _config_bind_ip(argc, argv, DNS_BIND_TYPE_TLS); -} - -static int _config_bind_ip_https(void *data, int argc, char *argv[]) -{ - return _config_bind_ip(argc, argv, DNS_BIND_TYPE_HTTPS); -} - -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_server_udp(void *data, int argc, char *argv[]) -{ - return _config_server(argc, argv, DNS_SERVER_UDP, DEFAULT_DNS_PORT); -} - -static int _config_server_tcp(void *data, int argc, char *argv[]) -{ - return _config_server(argc, argv, DNS_SERVER_TCP, DEFAULT_DNS_PORT); -} - -static int _config_server_tls(void *data, int argc, char *argv[]) -{ - return _config_server(argc, argv, DNS_SERVER_TLS, DEFAULT_DNS_TLS_PORT); -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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 = malloc(sizeof(*server)); - if (server == NULL) { - tlog(TLOG_WARN, "malloc memory failed."); - goto errout; - } - memset(server, 0, sizeof(*server)); - - 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; -} - -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; - } - - node = radix_lookup(tree, &prefix); - return node; -} - -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 = malloc(size); - if (!rule) { - return NULL; - } - memset(rule, 0, size); - rule->rule = ip_rule; - atomic_set(&rule->refcnt, 1); - return rule; -} - -static void *_new_dns_ip_rule(enum ip_rule ip_rule) -{ - return _new_dns_ip_rule_ext(ip_rule, 0); -} - -static void _dns_ip_rule_get(struct dns_ip_rule *rule) -{ - atomic_inc(&rule->refcnt); -} - -static 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); - } -} - -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 = malloc(size); - if (!rule) { - return NULL; - } - memset(rule, 0, size); - 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 = malloc(sizeof(*group_mac)); - if (group_mac == NULL) { - return NULL; - } - memset(group_mac, 0, sizeof(*group_mac)); - 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_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); -} - -static 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 = malloc(sizeof(*add_client_rules)); - if (add_client_rules == NULL) { - goto errout; - } - memset(add_client_rules, 0, sizeof(*add_client_rules)); - 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 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 = malloc(sizeof(*add_client_rules)); - if (add_client_rules == NULL) { - goto errout; - } - memset(add_client_rules, 0, sizeof(*add_client_rules)); - 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 _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; -} - -static int _config_qtype_soa(void *data, int argc, char *argv[]) -{ - return _conf_qtype_soa(_config_current_rule_group()->soa_table, argc, argv); -} - -static 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); - } -} - -static 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; -} - -static 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); -} - -static 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; -} - -static 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; dns_conf_response_mode_enum[i].name != NULL; i++) { - if (strcmp(mode, dns_conf_response_mode_enum[i].name) == 0) { - response_mode_type = dns_conf_response_mode_enum[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; -} - -static int _conf_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 = malloc(sizeof(*domain_set)); - if (domain_set == NULL) { - tlog(TLOG_ERROR, "cannot malloc memory."); - goto errout; - } - memset(domain_set, 0, sizeof(*domain_set)); - 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 = malloc(sizeof(*domain_set_name_list)); - if (domain_set_name_list == NULL) { - tlog(TLOG_ERROR, "cannot malloc memory."); - goto errout; - } - memset(domain_set_name_list, 0, sizeof(*domain_set_name_list)); - 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; -} - -static 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 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 int _config_ip_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear); -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); -} - -static 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 = malloc(sizeof(*add_ip_rules)); - if (add_ip_rules == NULL) { - goto errout; - } - memset(add_ip_rules, 0, sizeof(*add_ip_rules)); - 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(const char *ip_cidr, enum ip_rule type, void *rule); -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); -} - -static 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 = malloc(sizeof(*add_ip_rules)); - if (add_ip_rules == NULL) { - goto errout; - } - memset(add_ip_rules, 0, sizeof(*add_ip_rules)); - 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 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; -} - -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); -} - -static int _config_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; -} - -static 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); -} - -static int _conf_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); -} - -static int _conf_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); -} - -static int _conf_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); -} - -static int _conf_ip_alias(void *data, int argc, char *argv[]) -{ - if (argc <= 2) { - return -1; - } - - return _config_ip_alias(argv[1], argv[2]); -} - -static int _conf_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 (_config_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 _conf_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 = malloc(sizeof(*ip_set)); - if (ip_set == NULL) { - tlog(TLOG_ERROR, "cannot malloc memory."); - goto errout; - } - memset(ip_set, 0, sizeof(*ip_set)); - 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 = malloc(sizeof(*ip_set_name_list)); - if (ip_set_name_list == NULL) { - tlog(TLOG_ERROR, "cannot malloc memory."); - goto errout; - } - memset(ip_set_name_list, 0, sizeof(*ip_set_name_list)); - 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; -} - -static 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; -} - -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; -} - -static 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); - } -} - -static int _conf_ddns_domain(void *data, int argc, char *argv[]) -{ - if (argc <= 1) { - tlog(TLOG_ERROR, "invalid parameter."); - return -1; - } - - const char *domain = argv[1]; - _config_domain_rule_flag_set(domain, DOMAIN_FLAG_SMARTDNS_DOMAIN, 0); - return 0; -} - -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_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}, - {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); - } - - /* 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; - } - 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; -} - -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 = malloc(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; -} - -static 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; -} - -static 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); - } -} - -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 = malloc(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_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++; - return 0; - -errout: - return -1; -} - -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; -} - -static 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; -} - -static int _config_foreach_file(const char *file_pattern, int (*callback)(const char *file, void *priv), void *priv) -{ - char file_path[DNS_MAX_PATH]; - char file_path_dir[DNS_MAX_PATH]; - 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; -} - -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; -} - -static int _conf_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); -} - -static 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 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; -} - -static 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 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; -} - -static struct dns_conf_plugin_conf *_config_get_plugin_conf(const char *key) -{ - uint32_t hash = 0; - struct dns_conf_plugin_conf *conf = NULL; - - hash = hash_string(key); - hash_for_each_possible(dns_conf_plugin_table.plugins_conf, conf, node, hash) - { - if (strncmp(conf->key, key, DNS_MAX_PATH) != 0) { - continue; - } - - return conf; - } - - return NULL; -} - -const char *dns_conf_get_plugin_conf(const char *key) -{ - struct dns_conf_plugin_conf *conf = _config_get_plugin_conf(key); - if (conf == NULL) { - return NULL; - } - - return conf->value; -} - -void dns_conf_clear_all_plugin_conf(void) -{ - _config_plugin_table_conf_destroy(); -} - -static int _config_plugin(void *data, int argc, char *argv[]) -{ -#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; - } - - conf_get_conf_fullpath(argv[1], file, sizeof(file)); - if (file[0] == '\0') { - tlog(TLOG_ERROR, "plugin: invalid parameter."); - goto errout; - } - - struct dns_conf_plugin *plugin = _config_get_plugin(file); - if (plugin != NULL) { - tlog(TLOG_ERROR, "plugin '%s' already exists.", file); - goto errout; - } - - if (access(file, F_OK) != 0) { - tlog(TLOG_ERROR, "plugin '%s' not exists.", file); - goto errout; - } - - plugin = malloc(sizeof(*plugin)); - if (plugin == NULL) { - goto errout; - } - memset(plugin, 0, sizeof(*plugin)); - 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; -} - -static int _config_plugin_conf_add(const char *key, const char *value) -{ - uint32_t hash = 0; - struct dns_conf_plugin_conf *conf = NULL; - - if (key == NULL || value == NULL) { - tlog(TLOG_ERROR, "invalid parameter."); - goto errout; - } - - conf = _config_get_plugin_conf(key); - if (conf == NULL) { - - hash = hash_string(key); - conf = malloc(sizeof(*conf)); - if (conf == NULL) { - goto errout; - } - memset(conf, 0, sizeof(*conf)); - safe_strncpy(conf->key, key, sizeof(conf->key) - 1); - hash_add(dns_conf_plugin_table.plugins_conf, &conf->node, hash); - } - safe_strncpy(conf->value, value, sizeof(conf->value) - 1); - - return 0; - -errout: - return -1; -} - -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; -} - -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 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); -} - -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 struct config_item _config_item[] = { - CONF_STRING("server-name", (char *)dns_conf.server_name, DNS_MAX_SERVER_NAME_LEN), - 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-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-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", _conf_whitelist_ip, NULL), - CONF_CUSTOM("ip-alias", _conf_ip_alias, NULL), - CONF_CUSTOM("ip-rules", _conf_ip_rules, NULL), - CONF_CUSTOM("ip-set", _conf_ip_set, NULL), - CONF_CUSTOM("bogus-nxdomain", _conf_bogus_nxdomain, NULL), - CONF_CUSTOM("ignore-ip", _conf_ip_ignore, NULL), - CONF_CUSTOM("edns-client-subnet", _conf_edns_client_subnet, NULL), - CONF_CUSTOM("domain-rules", _conf_domain_rules, NULL), - CONF_CUSTOM("domain-set", _conf_domain_set, NULL), - CONF_CUSTOM("ddns-domain", _conf_ddns_domain, NULL), - CONF_CUSTOM("dnsmasq-lease-file", _conf_dhcp_lease_dnsmasq_file, NULL), - CONF_CUSTOM("hosts-file", _conf_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(), -}; - -static int _conf_value_handler(const char *key, const char *value) -{ - if (strstr(key, ".") == NULL) { - return -1; - } - - _config_plugin_conf_add(key, value); - - return 0; -} - -static 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; -} - -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 = malloc(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[DNS_MAX_PATH]; - char file_path_dir[DNS_MAX_PATH]; - - 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, _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; - } - } - } - - last_group_info = _config_current_group(); - if (group_name != NULL) { - ret = _config_current_group_push(group_name); - 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; -} - -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); - 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; - } - - hash_init(dns_ipset_table.ipset); - hash_init(dns_nftset_table.nftset); - hash_init(dns_group_table.group); - hash_init(dns_hosts_table.hosts); - hash_init(dns_ptr_table.ptr); - hash_init(dns_domain_set_name_table.names); - hash_init(dns_ip_set_name_table.names); - hash_init(dns_conf_srv_record_table.srv); - hash_init(dns_conf_plugin_table.plugins); - hash_init(dns_conf_plugin_table.plugins_conf); - - if (_config_current_group_push_default() != 0) { - tlog(TLOG_ERROR, "init default group failed."); - return -1; - } - - return 0; -} - -static 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; -} - -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); - } -} - -static 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(); -} - -static 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); - } -} - -static void _config_plugin_table_conf_destroy(void) -{ - struct dns_conf_plugin_conf *plugin_conf = NULL; - struct hlist_node *tmp = NULL; - unsigned long i = 0; - - hash_for_each_safe(dns_conf_plugin_table.plugins_conf, i, tmp, plugin_conf, node) - { - hlist_del_init(&plugin_conf->node); - free(plugin_conf); - } -} - -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_srv_record_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 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); -} - -static 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; - - 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) { - 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; - } - dns_conf.has_icmp_check = 1; - } - - if (check_orders->orders[i].type == DOMAIN_CHECK_TCP) { - dns_conf.has_tcp_check = 1; - } - } - } - - if (print_log) { - tlog(TLOG_WARN, "speed check by ping is disabled because smartdns does not have network raw privileges"); - } - - return 0; -} - -static 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; - } - - return 0; -} - -static 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); -} - -static void _dns_conf_default_value_init(void) -{ - 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 * 1024; - dns_conf.log_level = TLOG_ERROR; - 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 = DOMAIN_CHECK_TCP; - dns_conf.default_check_orders.orders[1].tcp_port = 80; - dns_conf.default_check_orders.orders[2].type = DOMAIN_CHECK_TCP; - 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)); - - hash_table_init(conf_file_table, 8, malloc); - - 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 = 2048; /* 1MB memory */ - } else if (memsize <= 32 * 1024 * 1024) { - dns_conf.cachesize = 8192; /* 4MB memory*/ - } else if (memsize <= 64 * 1024 * 1024) { - dns_conf.cachesize = 16384; /* 8MB memory*/ - } else if (memsize <= 128 * 1024 * 1024) { - dns_conf.cachesize = 32768; /* 16MB memory*/ - } else if (memsize <= 256 * 1024 * 1024) { - dns_conf.cachesize = 65536; /* 32MB memory*/ - } else if (memsize <= 512 * 1024 * 1024) { - dns_conf.cachesize = 131072; /* 64MB memory*/ - } else { - dns_conf.cachesize = 262144; /* 128MB memory*/ - } -} - -static 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 (dns_conf.cachesize == 0 && group->dns_response_mode == DNS_RESPONSE_MODE_FASTEST_RESPONSE) { - group->dns_response_mode = DNS_RESPONSE_MODE_FASTEST_IP; - tlog(TLOG_WARN, "force set response of group %s to %s as cache size is 0", group->group_name, - dns_conf_response_mode_enum[group->dns_response_mode].name); - } - - 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; - } - } -} - -static int _dns_conf_load_post(void) -{ - _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(); - - _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; -} diff --git a/src/dns_conf/address.c b/src/dns_conf/address.c new file mode 100755 index 0000000000..024b90dea7 --- /dev/null +++ b/src/dns_conf/address.c @@ -0,0 +1,212 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; + ptr = strstr(ptr, ","); + + if (field == NULL || *field == '\0') { + break; + } + + 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 (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr) && ipv4_num < DNS_MAX_REPLY_IP_NUM) { + memcpy(ipv4_addr[ipv4_num], addr_in6->sin6_addr.s6_addr + 12, DNS_RR_A_LEN); + ipv4_num++; + } else 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; +} \ No newline at end of file diff --git a/src/dns_conf/address.h b/src/dns_conf/address.h new file mode 100755 index 0000000000..1d2e36d5b3 --- /dev/null +++ b/src/dns_conf/address.h @@ -0,0 +1,36 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/bind.c b/src/dns_conf/bind.c new file mode 100755 index 0000000000..f0b85b7649 --- /dev/null +++ b/src/dns_conf/bind.c @@ -0,0 +1,435 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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}, + {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; + } + 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); +} diff --git a/src/dns_conf/bind.h b/src/dns_conf/bind.h new file mode 100755 index 0000000000..744fe2e20d --- /dev/null +++ b/src/dns_conf/bind.h @@ -0,0 +1,40 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/bootstrap_dns.c b/src/dns_conf/bootstrap_dns.c new file mode 100755 index 0000000000..f9507db9f1 --- /dev/null +++ b/src/dns_conf/bootstrap_dns.c @@ -0,0 +1,44 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} \ No newline at end of file diff --git a/src/dns_conf/bootstrap_dns.h b/src/dns_conf/bootstrap_dns.h new file mode 100755 index 0000000000..e7a4ef0256 --- /dev/null +++ b/src/dns_conf/bootstrap_dns.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/client_rule.c b/src/dns_conf/client_rule.c new file mode 100755 index 0000000000..8e93629be8 --- /dev/null +++ b/src/dns_conf/client_rule.c @@ -0,0 +1,530 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(size); + if (!rule) { + return NULL; + } + memset(rule, 0, size); + 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 = malloc(sizeof(*group_mac)); + if (group_mac == NULL) { + return NULL; + } + memset(group_mac, 0, sizeof(*group_mac)); + 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 = malloc(sizeof(*add_client_rules)); + if (add_client_rules == NULL) { + goto errout; + } + memset(add_client_rules, 0, sizeof(*add_client_rules)); + 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 = malloc(sizeof(*add_client_rules)); + if (add_client_rules == NULL) { + goto errout; + } + memset(add_client_rules, 0, sizeof(*add_client_rules)); + 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; +} diff --git a/src/dns_conf/client_rule.h b/src/dns_conf/client_rule.h new file mode 100755 index 0000000000..dba6b271cf --- /dev/null +++ b/src/dns_conf/client_rule.h @@ -0,0 +1,40 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/client_subnet.c b/src/dns_conf/client_subnet.c new file mode 100755 index 0000000000..5e721a8b93 --- /dev/null +++ b/src/dns_conf/client_subnet.c @@ -0,0 +1,97 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); +} diff --git a/src/dns_conf/client_subnet.h b/src/dns_conf/client_subnet.h new file mode 100755 index 0000000000..13dfb79a5b --- /dev/null +++ b/src/dns_conf/client_subnet.h @@ -0,0 +1,36 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/cname.c b/src/dns_conf/cname.c new file mode 100755 index 0000000000..e293559155 --- /dev/null +++ b/src/dns_conf/cname.c @@ -0,0 +1,81 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} \ No newline at end of file diff --git a/src/dns_conf/cname.h b/src/dns_conf/cname.h new file mode 100755 index 0000000000..38a46671a6 --- /dev/null +++ b/src/dns_conf/cname.h @@ -0,0 +1,36 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/conf_file.c b/src/dns_conf/conf_file.c new file mode 100755 index 0000000000..6b0a743e48 --- /dev/null +++ b/src/dns_conf/conf_file.c @@ -0,0 +1,185 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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/lib/stringutil.h" +#include "smartdns/util.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, malloc); + + 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 = malloc(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; + } + } + } + + last_group_info = _config_current_group(); + if (group_name != NULL) { + ret = _config_current_group_push(group_name); + 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); +} diff --git a/src/dns_conf/conf_file.h b/src/dns_conf/conf_file.h new file mode 100755 index 0000000000..25bdb0412f --- /dev/null +++ b/src/dns_conf/conf_file.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/ddns_domain.c b/src/dns_conf/ddns_domain.c new file mode 100755 index 0000000000..bca9fd18a5 --- /dev/null +++ b/src/dns_conf/ddns_domain.c @@ -0,0 +1,41 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/dns_conf/ddns_domain.h b/src/dns_conf/ddns_domain.h new file mode 100755 index 0000000000..41576ff0f0 --- /dev/null +++ b/src/dns_conf/ddns_domain.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/dhcp_lease_dnsmasq.c b/src/dns_conf/dhcp_lease_dnsmasq.c new file mode 100755 index 0000000000..37946eda48 --- /dev/null +++ b/src/dns_conf/dhcp_lease_dnsmasq.c @@ -0,0 +1,128 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} \ No newline at end of file diff --git a/src/dns_conf/dhcp_lease_dnsmasq.h b/src/dns_conf/dhcp_lease_dnsmasq.h new file mode 100755 index 0000000000..12660e2b6b --- /dev/null +++ b/src/dns_conf/dhcp_lease_dnsmasq.h @@ -0,0 +1,36 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/dns64.c b/src/dns_conf/dns64.c new file mode 100755 index 0000000000..180557575c --- /dev/null +++ b/src/dns_conf/dns64.c @@ -0,0 +1,63 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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" + +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; +} \ No newline at end of file diff --git a/src/dns_conf/dns64.h b/src/dns_conf/dns64.h new file mode 100755 index 0000000000..ec771dc55c --- /dev/null +++ b/src/dns_conf/dns64.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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[]); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_conf/dns_conf.c b/src/dns_conf/dns_conf.c new file mode 100755 index 0000000000..95aee4e5ab --- /dev/null +++ b/src/dns_conf/dns_conf.c @@ -0,0 +1,472 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "smartdns/regexp.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 "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 struct config_item _config_item[] = { + CONF_STRING("server-name", (char *)dns_conf.server_name, DNS_MAX_SERVER_NAME_LEN), + 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-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("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; + } + + _config_plugin_conf_add(key, value); + + return 0; +} + +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_srv_record_table_init(); + _config_plugin_table_init(); + + if (_config_current_group_push_default() != 0) { + tlog(TLOG_ERROR, "init default group failed."); + return -1; + } + +dns_regexp_init(); + 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_srv_record_table_destroy(); + _config_plugin_table_destroy(); + _config_plugin_table_conf_destroy(); + + dns_conf.server_num = 0; + dns_server_bind_destroy(); +dns_regexp_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) +{ + 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.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 = DOMAIN_CHECK_TCP; + dns_conf.default_check_orders.orders[1].tcp_port = 80; + dns_conf.default_check_orders.orders[2].type = DOMAIN_CHECK_TCP; + 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 = 2048; /* 1MB memory */ + } else if (memsize <= 32 * 1024 * 1024) { + dns_conf.cachesize = 8192; /* 4MB memory*/ + } else if (memsize <= 64 * 1024 * 1024) { + dns_conf.cachesize = 16384; /* 8MB memory*/ + } else if (memsize <= 128 * 1024 * 1024) { + dns_conf.cachesize = 32768; /* 16MB memory*/ + } else if (memsize <= 256 * 1024 * 1024) { + dns_conf.cachesize = 65536; /* 32MB memory*/ + } else if (memsize <= 512 * 1024 * 1024) { + dns_conf.cachesize = 131072; /* 64MB memory*/ + } else { + dns_conf.cachesize = 262144; /* 128MB memory*/ + } +} + +static int _dns_conf_load_post(void) +{ + _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(); + + _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; +} diff --git a/src/dns_conf/dns_conf.h b/src/dns_conf/dns_conf.h new file mode 100755 index 0000000000..d91dfff6a8 --- /dev/null +++ b/src/dns_conf/dns_conf.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/dns_conf_group.c b/src/dns_conf/dns_conf_group.c new file mode 100755 index 0000000000..694403c767 --- /dev/null +++ b/src/dns_conf/dns_conf_group.c @@ -0,0 +1,418 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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" + +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_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; + + soa_table = malloc(soa_talbe_size); + if (soa_table == NULL) { + tlog(TLOG_WARN, "malloc qtype soa table failed."); + return -1; + } + group_rule->soa_table = soa_table; + + if (_config_current_rule_group() != NULL) { + /* copy parent group data. */ + memcpy(&group_rule->copy_data_section_begin, &_config_current_rule_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, _config_current_rule_group()->soa_table, soa_talbe_size); + return 0; + } + + memset(soa_table, 0, soa_talbe_size); + 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; + group_rule->dns_serve_expired_ttl = 24 * 3600 * 3; + 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) +{ + struct dns_conf_group_info *group_info = NULL; + struct dns_conf_group *group_rule = NULL; + + group_info = malloc(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; + } + } + + memset(group_info, 0, sizeof(*group_info)); + 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); +} + +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_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 = malloc(sizeof(*rule_group)); + if (rule_group == NULL) { + return NULL; + } + + memset(rule_group, 0, sizeof(*rule_group)); + 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; + } + } +} \ No newline at end of file diff --git a/src/dns_conf/dns_conf_group.h b/src/dns_conf/dns_conf_group.h new file mode 100755 index 0000000000..49bec3525a --- /dev/null +++ b/src/dns_conf/dns_conf_group.h @@ -0,0 +1,67 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; + struct dns_conf_group *rule; +}; + +extern struct dns_conf_rule dns_conf_rule; + +#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); +int _config_current_group_push_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 diff --git a/src/dns_conf/domain_rule.c b/src/dns_conf/domain_rule.c new file mode 100755 index 0000000000..ce55791219 --- /dev/null +++ b/src/dns_conf/domain_rule.c @@ -0,0 +1,901 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 + +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; + } + + switch (domain_rule) { + case DOMAIN_RULE_FLAGS: + size = sizeof(struct dns_rule_flags); + break; + case DOMAIN_RULE_ADDRESS_IPV4: + size = sizeof(struct dns_rule_address_IPV4); + break; + case DOMAIN_RULE_ADDRESS_IPV6: + size = sizeof(struct dns_rule_address_IPV6); + break; + case DOMAIN_RULE_IPSET: + case DOMAIN_RULE_IPSET_IPV4: + case DOMAIN_RULE_IPSET_IPV6: + size = sizeof(struct dns_ipset_rule); + break; + case DOMAIN_RULE_NFTSET_IP: + case DOMAIN_RULE_NFTSET_IP6: + size = sizeof(struct dns_nftset_rule); + break; + case DOMAIN_RULE_NAMESERVER: + size = sizeof(struct dns_nameserver_rule); + break; + case DOMAIN_RULE_GROUP: + size = sizeof(struct dns_group_rule); + break; + case DOMAIN_RULE_CHECKSPEED: + size = sizeof(struct dns_domain_check_orders); + break; + case DOMAIN_RULE_RESPONSE_MODE: + size = sizeof(struct dns_response_mode_rule); + break; + case DOMAIN_RULE_CNAME: + size = sizeof(struct dns_cname_rule); + break; + case DOMAIN_RULE_HTTPS: + size = sizeof(struct dns_https_record_rule); + break; + case DOMAIN_RULE_TTL: + size = sizeof(struct dns_ttl_rule); + break; + default: + return NULL; + } + + size += ext_size; + rule = malloc(size); + if (!rule) { + return NULL; + } + memset(rule, 0, size); + 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); +} + +void _dns_rule_get(struct dns_rule *rule) +{ + atomic_inc(&rule->refcnt); +} + +void _dns_rule_put(struct dns_rule *rule) +{ + if (atomic_dec_and_test(&rule->refcnt)) { + 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: +if (_config_domain_rule_each_from_geosite(set_name_item->file, DNS_DOMAIN_SET_GEOSITE,callback, priv) != 0) { + return -1; +}; + break; + case DNS_DOMAIN_SET_GEOSITELIST: +if (_config_domain_rule_each_from_geosite(set_name_item->file, DNS_DOMAIN_SET_GEOSITELIST,callback, priv) != 0) { + return -1; +} + break; + + 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 (len >= domain_key_max_len - 3) { + tlog(TLOG_ERROR, "domain %s too long", domain); + 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 */ + domain_key[len + 1] = '.'; + len++; + } + + domain_key[len + 1] = 0; + domain_key[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]; + 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); +} + +static int _config_domain_rule_free(struct dns_domain_rule *domain_rule) +{ + int i = 0; + + if (domain_rule == NULL) { + return 0; + } + + for (i = 0; i < DOMAIN_RULE_MAX; 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]; + 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]; + 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 or create domain rule */ + domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); + if (domain_rule == NULL) { + add_domain_rule = malloc(sizeof(*add_domain_rule)); + if (add_domain_rule == NULL) { + goto errout; + } + memset(add_domain_rule, 0, sizeof(*add_domain_rule)); + domain_rule = add_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; + } + + domain_rule->sub_rule_only = sub_rule_only; + domain_rule->root_rule_only = root_rule_only; + + rule_flags = (struct dns_rule_flags *)domain_rule->rules[DOMAIN_RULE_FLAGS]; + 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_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]; + 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 or create domain rule */ + domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len); + if (domain_rule == NULL) { + add_domain_rule = malloc(sizeof(*add_domain_rule)); + if (add_domain_rule == NULL) { + goto errout; + } + memset(add_domain_rule, 0, sizeof(*add_domain_rule)); + domain_rule = add_domain_rule; + } + + /* add new rule to domain */ + if (domain_rule->rules[type]) { + _dns_rule_put(domain_rule->rules[type]); + domain_rule->rules[type] = NULL; + } + + domain_rule->rules[type] = rule; + domain_rule->sub_rule_only = sub_rule_only; + domain_rule->root_rule_only = root_rule_only; + _dns_rule_get(rule); + + /* 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 -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_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; +} + +static 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}, + {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); + } + + /* 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; + } + 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; +} diff --git a/src/dns_conf/domain_rule.h b/src/dns_conf/domain_rule.h new file mode 100755 index 0000000000..de957b8d0c --- /dev/null +++ b/src/dns_conf/domain_rule.h @@ -0,0 +1,45 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_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); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_conf/domain_set.c b/src/dns_conf/domain_set.c new file mode 100755 index 0000000000..0d22792388 --- /dev/null +++ b/src/dns_conf/domain_set.c @@ -0,0 +1,155 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 +#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 = malloc(sizeof(*domain_set)); + if (domain_set == NULL) { + tlog(TLOG_ERROR, "cannot malloc memory."); + goto errout; + } + memset(domain_set, 0, sizeof(*domain_set)); + 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 if (strncmp(type, "geositelist", 11) == 0) { + domain_set->type = DNS_DOMAIN_SET_GEOSITELIST; + } 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 = malloc(sizeof(*domain_set_name_list)); + if (domain_set_name_list == NULL) { + tlog(TLOG_ERROR, "cannot malloc memory."); + goto errout; + } + memset(domain_set_name_list, 0, sizeof(*domain_set_name_list)); + 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); + } +} \ No newline at end of file diff --git a/src/dns_conf/domain_set.h b/src/dns_conf/domain_set.h new file mode 100755 index 0000000000..da20d2e65c --- /dev/null +++ b/src/dns_conf/domain_set.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/get_domain.c b/src/dns_conf/get_domain.c new file mode 100755 index 0000000000..e5f904e379 --- /dev/null +++ b/src/dns_conf/get_domain.c @@ -0,0 +1,84 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} \ No newline at end of file diff --git a/src/dns_conf/get_domain.h b/src/dns_conf/get_domain.h new file mode 100755 index 0000000000..2dec951d5a --- /dev/null +++ b/src/dns_conf/get_domain.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/group.c b/src/dns_conf/group.c new file mode 100755 index 0000000000..5fa96e5483 --- /dev/null +++ b/src/dns_conf/group.c @@ -0,0 +1,129 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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[]) +{ + const char *group_name = NULL; + if (argc < 2) { + return -1; + } + + group_name = argv[1]; + if (group_name[0] == '\0') { + group_name = NULL; + } + + if (_config_current_group_push(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; +} \ No newline at end of file diff --git a/src/dns_conf/group.h b/src/dns_conf/group.h new file mode 100755 index 0000000000..56fc4aac58 --- /dev/null +++ b/src/dns_conf/group.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/host_file.c b/src/dns_conf/host_file.c new file mode 100755 index 0000000000..3b2569191c --- /dev/null +++ b/src/dns_conf/host_file.c @@ -0,0 +1,258 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "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 = malloc(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; +} + +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++; + return 0; + +errout: + return -1; +} diff --git a/src/dns_conf/host_file.h b/src/dns_conf/host_file.h new file mode 100755 index 0000000000..b0698126fb --- /dev/null +++ b/src/dns_conf/host_file.h @@ -0,0 +1,40 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/https_record.c b/src/dns_conf/https_record.c new file mode 100755 index 0000000000..b400f10a0a --- /dev/null +++ b/src/dns_conf/https_record.c @@ -0,0 +1,220 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; + 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; + + safe_strncpy(buff, host, sizeof(buff)); + + https_record_rule = _new_dns_rule(type); + if (https_record_rule == 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; + } + } else if (strncmp(key, "target", sizeof("target")) == 0) { + safe_strncpy(https_record_rule->record.target, val, DNS_MAX_CONF_CNAME_LEN); + https_record_rule->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 { + mode_type = 1; + https_record_rule->record.enable = 1; + if (strncmp(key, "priority", sizeof("priority")) == 0) { + priority = atoi(val); + } else if (strncmp(key, "port", sizeof("port")) == 0) { + https_record_rule->record.port = atoi(val); + + } else if (strncmp(key, "alpn", sizeof("alpn")) == 0) { + int alpn_len = _conf_domain_rule_https_copy_alpn(https_record_rule->record.alpn, DNS_MAX_ALPN_LEN, val); + if (alpn_len <= 0) { + tlog(TLOG_ERROR, "invalid option value for %s.", key); + goto errout; + } + https_record_rule->record.alpn_len = alpn_len; + } else if (strncmp(key, "ech", sizeof("ech")) == 0) { + int ech_len = SSL_base64_decode(val, https_record_rule->record.ech, DNS_MAX_ECH_LEN); + if (ech_len < 0) { + tlog(TLOG_ERROR, "invalid option value for %s.", key); + goto errout; + } + https_record_rule->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, https_record_rule->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; + } + https_record_rule->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, https_record_rule->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; + } + https_record_rule->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; + } + } + + https_record_rule->record.priority = priority; + + 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 (https_record_rule) { + _dns_rule_put(&https_record_rule->head); + } + + 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; +} \ No newline at end of file diff --git a/src/dns_conf/https_record.h b/src/dns_conf/https_record.h new file mode 100755 index 0000000000..b54bd78d23 --- /dev/null +++ b/src/dns_conf/https_record.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/ip_alias.c b/src/dns_conf/ip_alias.c new file mode 100755 index 0000000000..96c99cd8af --- /dev/null +++ b/src/dns_conf/ip_alias.c @@ -0,0 +1,90 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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]); +} \ No newline at end of file diff --git a/src/dns_conf/ip_alias.h b/src/dns_conf/ip_alias.h new file mode 100755 index 0000000000..775b65040c --- /dev/null +++ b/src/dns_conf/ip_alias.h @@ -0,0 +1,36 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/ip_rule.c b/src/dns_conf/ip_rule.c new file mode 100755 index 0000000000..868f219b48 --- /dev/null +++ b/src/dns_conf/ip_rule.c @@ -0,0 +1,477 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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) +{ + iplist->ipaddr = realloc(iplist->ipaddr, (iplist->ipaddr_num + 1) * sizeof(struct dns_iplist_ip_address)); + if (iplist->ipaddr == NULL) { + return; + } + 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; + } + + 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 = malloc(sizeof(*add_ip_rules)); + if (add_ip_rules == NULL) { + goto errout; + } + memset(add_ip_rules, 0, sizeof(*add_ip_rules)); + 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 = malloc(sizeof(*add_ip_rules)); + if (add_ip_rules == NULL) { + goto errout; + } + memset(add_ip_rules, 0, sizeof(*add_ip_rules)); + 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 = malloc(size); + if (!rule) { + return NULL; + } + memset(rule, 0, size); + 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; +} diff --git a/src/dns_conf/ip_rule.h b/src/dns_conf/ip_rule.h new file mode 100755 index 0000000000..6f52d32f9d --- /dev/null +++ b/src/dns_conf/ip_rule.h @@ -0,0 +1,51 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/ip_set.c b/src/dns_conf/ip_set.c new file mode 100755 index 0000000000..815fd7643e --- /dev/null +++ b/src/dns_conf/ip_set.c @@ -0,0 +1,155 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 +#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 = malloc(sizeof(*ip_set)); + if (ip_set == NULL) { + tlog(TLOG_ERROR, "cannot malloc memory."); + goto errout; + } + memset(ip_set, 0, sizeof(*ip_set)); + 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 = malloc(sizeof(*ip_set_name_list)); + if (ip_set_name_list == NULL) { + tlog(TLOG_ERROR, "cannot malloc memory."); + goto errout; + } + memset(ip_set_name_list, 0, sizeof(*ip_set_name_list)); + 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); + } +} \ No newline at end of file diff --git a/src/dns_conf/ip_set.h b/src/dns_conf/ip_set.h new file mode 100755 index 0000000000..a0d54bbf14 --- /dev/null +++ b/src/dns_conf/ip_set.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/ipset.c b/src/dns_conf/ipset.c new file mode 100755 index 0000000000..d212a242c3 --- /dev/null +++ b/src/dns_conf/ipset.c @@ -0,0 +1,261 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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" + +/* 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 = malloc(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; +} \ No newline at end of file diff --git a/src/dns_conf/ipset.h b/src/dns_conf/ipset.h new file mode 100755 index 0000000000..a8ee361a5a --- /dev/null +++ b/src/dns_conf/ipset.h @@ -0,0 +1,42 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/nameserver.c b/src/dns_conf/nameserver.c new file mode 100755 index 0000000000..e8bf5cd7f6 --- /dev/null +++ b/src/dns_conf/nameserver.c @@ -0,0 +1,83 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/dns_conf/nameserver.h b/src/dns_conf/nameserver.h new file mode 100755 index 0000000000..5b9bee8005 --- /dev/null +++ b/src/dns_conf/nameserver.h @@ -0,0 +1,36 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/nftset.c b/src/dns_conf/nftset.c new file mode 100755 index 0000000000..03a714765d --- /dev/null +++ b/src/dns_conf/nftset.c @@ -0,0 +1,317 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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" + +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 = malloc(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; +} diff --git a/src/dns_conf/nftset.h b/src/dns_conf/nftset.h new file mode 100755 index 0000000000..721fc7b8c7 --- /dev/null +++ b/src/dns_conf/nftset.h @@ -0,0 +1,42 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/plugin.c b/src/dns_conf/plugin.c new file mode 100755 index 0000000000..3cb8ffb7e7 --- /dev/null +++ b/src/dns_conf/plugin.c @@ -0,0 +1,192 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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" + +struct dns_conf_plugin_table dns_conf_plugin_table; + +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; +} + +static struct dns_conf_plugin_conf *_config_get_plugin_conf(const char *key) +{ + uint32_t hash = 0; + struct dns_conf_plugin_conf *conf = NULL; + + hash = hash_string(key); + hash_for_each_possible(dns_conf_plugin_table.plugins_conf, conf, node, hash) + { + if (strncmp(conf->key, key, DNS_MAX_PATH) != 0) { + continue; + } + + return conf; + } + + return NULL; +} + +const char *dns_conf_get_plugin_conf(const char *key) +{ + struct dns_conf_plugin_conf *conf = _config_get_plugin_conf(key); + if (conf == NULL) { + return NULL; + } + + return conf->value; +} + +int _config_plugin(void *data, int argc, char *argv[]) +{ +#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; + } + + conf_get_conf_fullpath(argv[1], file, sizeof(file)); + if (file[0] == '\0') { + tlog(TLOG_ERROR, "plugin: invalid parameter."); + goto errout; + } + + struct dns_conf_plugin *plugin = _config_get_plugin(file); + if (plugin != NULL) { + tlog(TLOG_ERROR, "plugin '%s' already exists.", file); + goto errout; + } + + if (access(file, F_OK) != 0) { + tlog(TLOG_ERROR, "plugin '%s' not exists.", file); + goto errout; + } + + plugin = malloc(sizeof(*plugin)); + if (plugin == NULL) { + goto errout; + } + memset(plugin, 0, sizeof(*plugin)); + 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) +{ + uint32_t hash = 0; + struct dns_conf_plugin_conf *conf = NULL; + + if (key == NULL || value == NULL) { + tlog(TLOG_ERROR, "invalid parameter."); + goto errout; + } + + conf = _config_get_plugin_conf(key); + if (conf == NULL) { + + hash = hash_string(key); + conf = malloc(sizeof(*conf)); + if (conf == NULL) { + goto errout; + } + memset(conf, 0, sizeof(*conf)); + safe_strncpy(conf->key, key, sizeof(conf->key) - 1); + hash_add(dns_conf_plugin_table.plugins_conf, &conf->node, hash); + } + safe_strncpy(conf->value, value, sizeof(conf->value) - 1); + + return 0; + +errout: + return -1; +} + +void _config_plugin_table_init(void) +{ + hash_init(dns_conf_plugin_table.plugins); + hash_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) +{ + struct dns_conf_plugin_conf *plugin_conf = NULL; + struct hlist_node *tmp = NULL; + unsigned long i = 0; + + hash_for_each_safe(dns_conf_plugin_table.plugins_conf, i, tmp, plugin_conf, node) + { + hlist_del_init(&plugin_conf->node); + free(plugin_conf); + } +} + +void dns_conf_clear_all_plugin_conf(void) +{ + _config_plugin_table_conf_destroy(); +} \ No newline at end of file diff --git a/src/dns_conf/plugin.h b/src/dns_conf/plugin.h new file mode 100755 index 0000000000..04d8969cd5 --- /dev/null +++ b/src/dns_conf/plugin.h @@ -0,0 +1,44 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/proxy_names.c b/src/dns_conf/proxy_names.c new file mode 100755 index 0000000000..2e0847ffdc --- /dev/null +++ b/src/dns_conf/proxy_names.c @@ -0,0 +1,117 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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" + +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 = malloc(sizeof(*proxy)); + if (proxy == NULL) { + goto errout; + } + + memset(proxy, 0, sizeof(*proxy)); + 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); + } +} diff --git a/src/dns_conf/proxy_names.h b/src/dns_conf/proxy_names.h new file mode 100755 index 0000000000..eb488fd1a2 --- /dev/null +++ b/src/dns_conf/proxy_names.h @@ -0,0 +1,37 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/proxy_server.c b/src/dns_conf/proxy_server.c new file mode 100755 index 0000000000..09f89f509b --- /dev/null +++ b/src/dns_conf/proxy_server.c @@ -0,0 +1,124 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(sizeof(*server)); + if (server == NULL) { + tlog(TLOG_WARN, "malloc memory failed."); + goto errout; + } + memset(server, 0, sizeof(*server)); + + 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; +} diff --git a/src/dns_conf/proxy_server.h b/src/dns_conf/proxy_server.h new file mode 100755 index 0000000000..e63f3649e4 --- /dev/null +++ b/src/dns_conf/proxy_server.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/ptr.c b/src/dns_conf/ptr.c new file mode 100755 index 0000000000..3be9501658 --- /dev/null +++ b/src/dns_conf/ptr.c @@ -0,0 +1,147 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(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); + } +} diff --git a/src/dns_conf/ptr.h b/src/dns_conf/ptr.h new file mode 100755 index 0000000000..b1a4a4221e --- /dev/null +++ b/src/dns_conf/ptr.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/qtype_soa.c b/src/dns_conf/qtype_soa.c new file mode 100755 index 0000000000..facd7aa9cc --- /dev/null +++ b/src/dns_conf/qtype_soa.c @@ -0,0 +1,94 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); +} \ No newline at end of file diff --git a/src/dns_conf/qtype_soa.h b/src/dns_conf/qtype_soa.h new file mode 100755 index 0000000000..d4f17cef38 --- /dev/null +++ b/src/dns_conf/qtype_soa.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/server.c b/src/dns_conf/server.c new file mode 100755 index 0000000000..c3d3989a1b --- /dev/null +++ b/src/dns_conf/server.c @@ -0,0 +1,397 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} \ No newline at end of file diff --git a/src/dns_conf/server.h b/src/dns_conf/server.h new file mode 100755 index 0000000000..6927009746 --- /dev/null +++ b/src/dns_conf/server.h @@ -0,0 +1,39 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/server_group.c b/src/dns_conf/server_group.c new file mode 100755 index 0000000000..1b4c845237 --- /dev/null +++ b/src/dns_conf/server_group.c @@ -0,0 +1,111 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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" + +/* 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 = malloc(sizeof(*group)); + if (group == NULL) { + goto errout; + } + + memset(group, 0, sizeof(*group)); + 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); + } +} \ No newline at end of file diff --git a/src/dns_conf/server_group.h b/src/dns_conf/server_group.h new file mode 100755 index 0000000000..ad0a8ebc1a --- /dev/null +++ b/src/dns_conf/server_group.h @@ -0,0 +1,52 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/set_file.c b/src/dns_conf/set_file.c new file mode 100755 index 0000000000..116b43db79 --- /dev/null +++ b/src/dns_conf/set_file.c @@ -0,0 +1,203 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_domain_rule_each_from_geosite(const char *file, int type, set_rule_add_func callback, void *priv) +{ + FILE *fp = NULL; + char line[MAX_LINE_LEN]; + char domain[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, "%255s", domain); + if (filed_num <= 0) { + continue; + } + + if (domain[0] == '#' || domain[0] == '\n') { + continue; + } + + char buf[DNS_MAX_CNAME_LEN]; + memset(buf, 0 ,sizeof(buf)); + if (strncmp(domain, "full:", 5)==0) { + safe_strncpy(buf, &domain[5], DNS_MAX_CNAME_LEN); + sprintf(domain,"%s",buf); + } + + if (strncmp(domain, "domain:", 7)==0) { + safe_strncpy(buf, &domain[7], DNS_MAX_CNAME_LEN); + sprintf(domain,"%s",buf); + } + + if (strncmp(domain, "keyword:", 8)==0) { + if (type==DNS_DOMAIN_SET_GEOSITELIST) + continue; + + safe_strncpy(buf, &domain[8], DNS_MAX_CNAME_LEN); + sprintf(domain,"^.*%s.*$", buf); + if (dns_regexp_insert(domain) !=0 ) { + tlog(TLOG_WARN, "insert regexp %s failed at file %s line %d.", domain, file, line_no); + continue; + } + } + + if (strncmp(domain, "regexp:", 7)==0) { + if (type==DNS_DOMAIN_SET_GEOSITELIST) + continue; + + safe_strncpy(buf, &domain[7], DNS_MAX_CNAME_LEN); + sprintf(domain,"%s",buf); + if (dns_regexp_insert(domain) !=0 ) { + tlog(TLOG_WARN, "insert regexp %s failed at file %s line %d.", domain, file, line_no); + 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_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)) { + line_no++; + filed_num = sscanf(line, "%255s", value); + if (filed_num <= 0) { + continue; + } + + if (value[0] == '#' || value[0] == '\n') { + continue; + } + + ret = callback(value, 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; +} diff --git a/src/dns_conf/set_file.h b/src/dns_conf/set_file.h new file mode 100755 index 0000000000..b006c3794f --- /dev/null +++ b/src/dns_conf/set_file.h @@ -0,0 +1,40 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_domain_rule_each_from_geosite(const char *file, int type, set_rule_add_func callback, void *priv); +int _config_set_rule_each_from_list(const char *file, set_rule_add_func callback, void *priv); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_conf/smartdns_domain.c b/src/dns_conf/smartdns_domain.c new file mode 100755 index 0000000000..4415506f04 --- /dev/null +++ b/src/dns_conf/smartdns_domain.c @@ -0,0 +1,68 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); +} diff --git a/src/dns_conf/smartdns_domain.h b/src/dns_conf/smartdns_domain.h new file mode 100755 index 0000000000..2807394307 --- /dev/null +++ b/src/dns_conf/smartdns_domain.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_conf/speed_check_mode.c b/src/dns_conf/speed_check_mode.c new file mode 100755 index 0000000000..aaf575c71a --- /dev/null +++ b/src/dns_conf/speed_check_mode.c @@ -0,0 +1,173 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_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; + ptr = strstr(ptr, ","); + if (field == NULL || order >= DOMAIN_CHECK_NUM) { + return 0; + } + + 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") == 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; + + 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) { + 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; + } + dns_conf.has_icmp_check = 1; + } + + if (check_orders->orders[i].type == DOMAIN_CHECK_TCP) { + dns_conf.has_tcp_check = 1; + } + } + } + + if (print_log) { + tlog(TLOG_WARN, "speed check by ping is disabled 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; + } + + return 0; +} diff --git a/src/dns_conf/speed_check_mode.h b/src/dns_conf/speed_check_mode.h new file mode 100755 index 0000000000..ec312288d6 --- /dev/null +++ b/src/dns_conf/speed_check_mode.h @@ -0,0 +1,40 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_conf/srv_record.c b/src/dns_conf/srv_record.c new file mode 100755 index 0000000000..2f7350645d --- /dev/null +++ b/src/dns_conf/srv_record.c @@ -0,0 +1,165 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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" + +/* SRV-HOST */ +struct dns_srv_record_table dns_conf_srv_record_table; + +struct dns_srv_records *dns_server_get_srv_record(const char *domain) +{ + uint32_t key = 0; + + key = hash_string(domain); + struct dns_srv_records *srv_records = NULL; + hash_for_each_possible(dns_conf_srv_record_table.srv, srv_records, node, key) + { + if (strncmp(srv_records->domain, domain, DNS_MAX_CONF_CNAME_LEN) == 0) { + return srv_records; + } + } + + return NULL; +} + +static int _confg_srv_record_add(const char *domain, const char *host, unsigned short priority, unsigned short weight, + unsigned short port) +{ + struct dns_srv_records *srv_records = NULL; + struct dns_srv_record *srv_record = NULL; + uint32_t key = 0; + + srv_records = dns_server_get_srv_record(domain); + if (srv_records == NULL) { + srv_records = malloc(sizeof(*srv_records)); + if (srv_records == NULL) { + goto errout; + } + memset(srv_records, 0, sizeof(*srv_records)); + safe_strncpy(srv_records->domain, domain, DNS_MAX_CONF_CNAME_LEN); + INIT_LIST_HEAD(&srv_records->list); + key = hash_string(domain); + hash_add(dns_conf_srv_record_table.srv, &srv_records->node, key); + } + + srv_record = malloc(sizeof(*srv_record)); + if (srv_record == NULL) { + goto errout; + } + memset(srv_record, 0, sizeof(*srv_record)); + 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_records->list); + + return 0; +errout: + if (srv_record != 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; +} + +void _config_srv_record_table_init(void) +{ + hash_init(dns_conf_srv_record_table.srv); +} + +void _config_srv_record_table_destroy(void) +{ + struct dns_srv_records *srv_records = NULL; + struct dns_srv_record *srv_record, *tmp1 = NULL; + struct hlist_node *tmp = NULL; + unsigned int i; + + hash_for_each_safe(dns_conf_srv_record_table.srv, i, tmp, srv_records, node) + { + list_for_each_entry_safe(srv_record, tmp1, &srv_records->list, list) + { + list_del(&srv_record->list); + free(srv_record); + } + + hlist_del_init(&srv_records->node); + free(srv_records); + } +} \ No newline at end of file diff --git a/src/dns_conf/srv_record.h b/src/dns_conf/srv_record.h new file mode 100755 index 0000000000..48d29ecf9b --- /dev/null +++ b/src/dns_conf/srv_record.h @@ -0,0 +1,37 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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[]); + +void _config_srv_record_table_init(void); +void _config_srv_record_table_destroy(void); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_plugin.c b/src/dns_plugin.c old mode 100644 new mode 100755 index 6b0880bf55..af0d50c821 --- a/src/dns_plugin.c +++ b/src/dns_plugin.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2023 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,21 +16,22 @@ * along with this program. If not, see . */ -#include "dns_plugin.h" +#define _GNU_SOURCE -#include "dns_conf.h" -#include "include/conf.h" -#include "include/hashtable.h" -#include "include/list.h" -#include "util.h" +#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 "tlog.h" - struct dns_plugin_ops { struct list_head list; struct smartdns_operations ops; diff --git a/src/dns_server.c b/src/dns_server.c deleted file mode 100644 index 3e8ecc2423..0000000000 --- a/src/dns_server.c +++ /dev/null @@ -1,9644 +0,0 @@ -/************************************************************************* - * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . - * - * smartdns is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * smartdns is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 "atomic.h" -#include "dns.h" -#include "dns_cache.h" -#include "dns_client.h" -#include "dns_conf.h" -#include "dns_plugin.h" -#include "dns_stats.h" -#include "fast_ping.h" -#include "hashtable.h" -#include "http_parse.h" -#include "list.h" -#include "nftset.h" -#include "tlog.h" -#include "util.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DNS_MAX_EVENTS 256 -#define IPV6_READY_CHECK_TIME 180 -#define DNS_SERVER_TMOUT_TTL (5 * 60) -#define DNS_SERVER_FAIL_TTL (60) -#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 (250) -#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_MAX_NUM (1024 * 8) -#define DNS_SERVER_NEIGHBOR_CACHE_TIMEOUT (3600 * 1) -#define DNS_SERVER_NEIGHBOR_CACHE_NOMAC_TIMEOUT 60 - -#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; - -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; - 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; -}; - -/* 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 { - 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 { - 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; -}; - -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]; - 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 dns_request_https *https_svcb; - - 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 dns_srv_records *srv_records; - - 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 */ - 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; - - 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; -}; - -static int is_server_init; -static struct dns_server server; - -static tlog_log *dns_audit; - -static int is_ipv6_ready; - -static int _dns_server_prefetch_request(char *domain, dns_type_t qtype, - struct dns_server_query_option *server_query_option, int prefetch_flags); -static int _dns_server_get_answer(struct dns_server_post_context *context); -static void _dns_server_request_get(struct dns_request *request); -static void _dns_server_request_release(struct dns_request *request); -static void _dns_server_request_release_complete(struct dns_request *request, int do_complete); -static int _dns_server_request_complete(struct dns_request *request); -static int _dns_server_reply_passthrough(struct dns_server_post_context *context); -static int _dns_server_do_query(struct dns_request *request, int skip_notify_event); -static int _dns_request_post(struct dns_server_post_context *context); -static int _dns_server_reply_all_pending_list(struct dns_request *request, struct dns_server_post_context *context); -static void *_dns_server_get_dns_rule(struct dns_request *request, enum domain_rule rule); -static int _dns_server_get_local_ttl(struct dns_request *request); -static const char *_dns_server_get_request_server_groupname(struct dns_request *request); -static int _dns_server_tcp_socket_send(struct dns_server_conn_tcp_client *tcp_client, void *data, int data_len); -static int _dns_server_update_request_connection_timeout(struct dns_server_conn_head *conn, int timeout); -static int _dns_server_cache_save(int check_lock); - -int dns_is_ipv6_ready(void) -{ - return is_ipv6_ready; -} - -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; -} - -static int _dns_server_has_bind_flag(struct dns_request *request, uint32_t flag) -{ - if (request->server_flags & flag) { - return 0; - } - - return -1; -} - -static 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; -} - -static 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; -} - -static 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) { - 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; -} - -static 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; -} - -static 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_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); -} - -static 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); -} - -static int _dns_server_is_dns64_request(struct dns_request *request) -{ - if (request->qtype != DNS_T_AAAA) { - return 0; - } - - if (request->dualstack_selection_query == 1) { - return 0; - } - - if (request->conf->dns_dns64.prefix_len <= 0) { - return 0; - } - - return 1; -} - -static 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 int _dns_server_is_return_soa_qtype(struct dns_request *request, dns_type_t qtype) -{ - struct dns_rule_flags *rule_flag = NULL; - unsigned int flags = 0; - - 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; - } - - rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); - if (rule_flag) { - flags = rule_flag->flags; - if (flags & DOMAIN_FLAG_ADDR_SOA) { - stats_inc(&dns_stats.request.blocked_count); - return 1; - } - - if (flags & DOMAIN_FLAG_ADDR_IGN) { - request->skip_qtype_soa = 1; - return 0; - } - - 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; - } - } else if (qtype == DNS_T_HTTPS) { - if (request->domain_rule.rules[DOMAIN_RULE_HTTPS] == NULL) { - return 1; - } - } - - return 0; -} - -static int _dns_server_is_return_soa(struct dns_request *request) -{ - return _dns_server_is_return_soa_qtype(request, request->qtype); -} - -static 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++; -} - -static 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 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; -} - -static 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; - } - - if (request->conn == NULL) { - 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 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 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; -} - -static int _dns_server_add_srv(struct dns_server_post_context *context) -{ - struct dns_request *request = context->request; - struct dns_srv_records *srv_records = request->srv_records; - struct dns_srv_record *srv_record = NULL; - int ret = 0; - - if (srv_records == NULL) { - return 0; - } - - list_for_each_entry(srv_record, &srv_records->list, list) - { - ret = dns_add_SRV(context->packet, DNS_RRS_AN, request->domain, request->ip_ttl, srv_record->priority, - srv_record->weight, srv_record->port, srv_record->host); - if (ret != 0) { - return -1; - } - } - - return 0; -} - -static int _dns_add_rrs_HTTPS(struct dns_server_post_context *context) -{ - struct dns_request *request = context->request; - struct dns_request_https *https_svcb = request->https_svcb; - int ret = 0; - struct dns_rr_nested param; - - if (https_svcb == NULL || request->qtype != DNS_T_HTTPS) { - return 0; - } - - 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 (request->has_ip) { - unsigned char *addr[1]; - addr[0] = request->ip_addr; - if (request->ip_addr_type == DNS_T_A) { - ret = dns_HTTPS_add_ipv4hint(¶m, addr, 1); - } - } - - 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 (request->has_ip) { - unsigned char *addr[1]; - addr[0] = request->ip_addr; - if (request->ip_addr_type == DNS_T_AAAA) { - ret = dns_HTTPS_add_ipv6hint(¶m, addr, 1); - } - } - - 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 (request->https_svcb != NULL) { - 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 (request->srv_records != NULL) { - 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 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->fd > 0) { - close(conn->fd); - conn->fd = -1; - } - - 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; - } - 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; - } - } - - list_del_init(&conn->list); - free(conn); -} - -static 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."); - } -} - -static 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; -} - -static 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; -} - -static 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); - 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; -} - -static 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; -} - -static 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_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 { - ret = -1; - } - - return ret; -} - -static inline 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: - 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; -} - -static 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; -} - -static 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((struct dns_cache_data *)cache_packet); - } - - return ret; -} - -static 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((struct dns_cache_data *)cache_packet); - } - - return ret; -} - -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_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); -} - -static 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_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; - } - - 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; -} - -static 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); - } - } -} - -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_https_param *p = NULL; - int priority = 0; - - int ret = dns_get_HTTPS_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_get_HTTPS_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(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; - if (dns_packet_update(context->inpacket, context->inpacket_len, ¶m) != 0) { - tlog(TLOG_DEBUG, "update packet info failed."); - } - - return 0; -} - -static 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(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; -} - -static int _dns_server_reply_SOA(int rcode, struct dns_request *request) -{ - /* return SOA record */ - request->rcode = rcode; - if (request->ip_ttl <= 0) { - request->ip_ttl = DNS_SERVER_SOA_TTL; - } - - _dns_server_setup_soa(request); - - 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; -} - -static 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; - - if (request->request_pending_list == NULL) { - return 0; - } - - pthread_mutex_lock(&server.request_pending_lock); - 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; -} - -static 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; -} - -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); -} - -static 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_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 || request->rcode == DNS_RC_NXDOMAIN) { - 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); -} - -static int _dns_server_request_complete(struct dns_request *request) -{ - return _dns_server_request_complete_with_all_IPs(request, 0); -} - -static 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 = malloc(sizeof(*addr_map)); - if (addr_map == NULL) { - pthread_mutex_unlock(&request->ip_map_lock); - tlog(TLOG_ERROR, "malloc addr map failed"); - return -1; - } - memset(addr_map, 0, sizeof(*addr_map)); - - 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; -} - -static 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_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); - request->ip_ttl = request->conf->dns_rr_ttl_min > 0 ? request->conf->dns_rr_ttl_min : DNS_SERVER_TMOUT_TTL; - 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); - request->ip_ttl = request->conf->dns_rr_ttl_min > 0 ? request->conf->dns_rr_ttl_min : DNS_SERVER_TMOUT_TTL; - 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; - } -} - -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); - if (request->https_svcb) { - free(request->https_svcb); - } - 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); -} - -static 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); -} - -static void _dns_server_request_release(struct dns_request *request) -{ - _dns_server_request_release_complete(request, 1); -} - -static 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; - } - - 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; -} - -static 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 = malloc(sizeof(*pending_list)); - if (pending_list == NULL) { - ret = -1; - goto out; - } - - memset(pending_list, 0, sizeof(*pending_list)); - 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; -} - -static struct dns_request *_dns_server_new_request(void) -{ - struct dns_request *request = NULL; - - request = malloc(sizeof(*request)); - if (request == NULL) { - tlog(TLOG_ERROR, "malloc request failed.\n"); - goto errout; - } - - memset(request, 0, sizeof(*request)); - 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); - 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; -} - -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 = request->ping_time; - - if (request == NULL) { - return; - } - - 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 + 12, 4); - 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; -} - -static 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); - if (ping_timeout > DNS_PING_TIMEOUT) { - ping_timeout = DNS_PING_TIMEOUT; - } else 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; - default: - break; - } - - return -1; -} - -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); -} - -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); -} - -static struct neighbor_cache_item *_dns_server_neighbor_cache_get_item(const uint8_t *net_addr, int net_addr_len) -{ - struct neighbor_cache_item *item, *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 = malloc(sizeof(*item)); - memset(item, 0, 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; -} - -static 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 < 2; i++) { - ret = netlink_get_neighbors(family, _dns_server_neighbors_callback, &args); - if (ret < 0) { - goto add_cache; - } - - if (ret != 1) { - /* FIXME: ugly force refresh NDP table by sending ICMP message.*/ - if (i == 0) { - char host[DNS_MAX_CNAME_LEN] = {0}; - if (family == AF_INET) { - snprintf(host, sizeof(host), "%d.%d.%d.%d", netaddr[0], netaddr[1], netaddr[2], netaddr[3]); - } else if (family == AF_INET6) { - snprintf(host, sizeof(host), - "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", netaddr[0], - netaddr[1], netaddr[2], netaddr[3], netaddr[4], netaddr[5], netaddr[6], netaddr[7], - netaddr[8], netaddr[9], netaddr[10], netaddr[11], netaddr[12], netaddr[13], netaddr[14], - netaddr[15]); - } - struct ping_host_struct *ping_host = fast_ping_start(PING_TYPE_ICMP, host, 0, 10, 1000, NULL, NULL); - if (ping_host) { - /* wait for NDP*/ - usleep(100); - fast_ping_stop(ping_host); - continue; - } - } - - 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); - return NULL; -} - -static 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) { - 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -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_https_param *p = NULL; - int priority = 0; - struct dns_request_https *https_svcb; - int no_ipv4 = 0; - int no_ipv6 = 0; - 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) { - no_ipv4 = 1; - } - - if (https_record_rule->filter.no_ipv6hint) { - no_ipv6 = 1; - } - } - - ret = dns_get_HTTPS_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 = request->https_svcb; - if (https_svcb == 0) { - /* ignore non-matched query type */ - tlog(TLOG_WARN, "https svcb not set"); - return -1; - } - - 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_get_HTTPS_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 (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; - } - } - - _dns_server_request_release(request); - - return 0; -} - -static 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; - } - - /* when QTYPE is HTTPS, check if support */ - if (request->qtype == DNS_T_HTTPS) { - int https_svcb_record_num = 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_HTTPS: { - https_svcb_record_num++; - if (https_svcb_record_num <= 1) { - continue; - } - - /* CURRENT NOT SUPPORT MUTI HTTPS RECORD */ - *need_passthrouh = 1; - return DNS_CLIENT_ACTION_OK; - } - } - } - } - } - - 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; - if (request->has_ip == 0) { - request->passthrough = 1; - _dns_server_request_complete(request); - } - } 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; - } - - 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; -} - -static 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]; - 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; - default: - if (ttl == 0) { - /* Get TTL */ - char tmpname[DNS_MAX_CNAME_LEN]; - char tmpbuf[DNS_MAX_CNAME_LEN]; - 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; -} - -static 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; -} - -static 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(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); -} - -static 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); -} - -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; -} - -static 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_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; - context.no_check_add_ip = 1; - _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; -} - -static 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_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; -} - -static 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; -} - -static 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_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, "%hhd.%hhd.%hhd.%hhd.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; -} - -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; -} - -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 = malloc(sizeof(struct local_addr_cache_item)); - if (addr_cache_item == NULL) { - return; - } - memset(addr_cache_item, 0, sizeof(struct local_addr_cache_item)); - } 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); -} - -static 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); - } - } - } -} - -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; -} - -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 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; -} - -static 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; -} - -static 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; -} - -static int _dns_server_process_DDR(struct dns_request *request) -{ - return _dns_server_reply_SOA(DNS_RC_NOERROR, request); -} - -static int _dns_server_process_srv(struct dns_request *request) -{ - struct dns_srv_records *srv_records = dns_server_get_srv_record(request->domain); - if (srv_records == NULL) { - return -1; - } - - request->rcode = DNS_RC_NOERROR; - request->ip_ttl = _dns_server_get_local_ttl(request); - request->srv_records = srv_records; - - 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; -} - -static 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; -} - -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]; - 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) -{ - struct dns_rule_flags *rule_flag = (struct dns_rule_flags *)request_domain_rule->rules[0]; - unsigned int flags = 0; - - if (rule_flag == NULL) { - return; - } - flags = rule_flag->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; - } - - if (domain_rule->sub_rule_only != domain_rule->root_rule_only) { - /* only subkey rule */ - if (domain_rule->sub_rule_only == 1 && is_subkey == 0) { - return 0; - } - - /* only root key rule */ - if (domain_rule->root_rule_only == 1 && is_subkey == 1) { - return 0; - } - } - - if (walk_args->rule_index >= 0) { - i = walk_args->rule_index; - } else { - i = 0; - } - - for (; i < DOMAIN_RULE_MAX; i++) { - if (domain_rule->rules[i] == NULL) { - if (walk_args->rule_index >= 0) { - break; - } - 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; -} - -static 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]; - struct rule_walk_args walk_args; - int matched_key_len = DNS_MAX_CNAME_LEN; - unsigned char matched_key[DNS_MAX_CNAME_LEN]; - 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; - - /* 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; - } - } -} - -static 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; -} - -static 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); -} - -static 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; -} - -static int _dns_server_pre_process_rule_flags(struct dns_request *request) -{ - struct dns_rule_flags *rule_flag = NULL; - unsigned int flags = 0; - int rcode = DNS_RC_NOERROR; - - /* get domain rule flag */ - rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); - if (rule_flag != NULL) { - flags = rule_flag->flags; - } - - 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -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; -} - -static int _dns_server_process_cname_pre(struct dns_request *request) -{ - struct dns_cname_rule *cname = NULL; - struct dns_rule_flags *rule_flag = NULL; - struct dns_request_domain_rule domain_rule; - - 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; - } - - 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; -} - -static 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; -} - -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 = malloc(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; - } - memset(new_addr_map, 0, sizeof(struct dns_ip_address)); - - 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; -} - -static 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); - - 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; -} - -static 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); - - if (request->qtype != DNS_T_HTTPS) { - return 0; - } - - if (request->https_svcb != NULL) { - return 0; - } - - request->https_svcb = malloc(sizeof(*request->https_svcb)); - if (request->https_svcb == NULL) { - return -1; - } - memset(request->https_svcb, 0, sizeof(*request->https_svcb)); - - if (https_record_rule == NULL) { - return 0; - } - - if (https_record_rule->record.enable == 0) { - return 0; - } - - safe_strncpy(request->https_svcb->domain, request->domain, sizeof(request->https_svcb->domain)); - safe_strncpy(request->https_svcb->target, https_record_rule->record.target, sizeof(request->https_svcb->target)); - request->https_svcb->priority = https_record_rule->record.priority; - request->https_svcb->port = https_record_rule->record.port; - memcpy(request->https_svcb->ech, https_record_rule->record.ech, https_record_rule->record.ech_len); - request->https_svcb->ech_len = https_record_rule->record.ech_len; - memcpy(request->https_svcb->alpn, https_record_rule->record.alpn, sizeof(request->https_svcb->alpn)); - request->https_svcb->alpn_len = https_record_rule->record.alpn_len; - if (https_record_rule->record.has_ipv4) { - memcpy(request->ip_addr, https_record_rule->record.ipv4_addr, DNS_RR_A_LEN); - request->ip_addr_type = DNS_T_A; - request->has_ip = 1; - } else if (https_record_rule->record.has_ipv6) { - memcpy(request->ip_addr, https_record_rule->record.ipv6_addr, DNS_RR_AAAA_LEN); - request->ip_addr_type = DNS_T_AAAA; - request->has_ip = 1; - } - - request->rcode = DNS_RC_NOERROR; - - return -1; -} - -static 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; -} - -static 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; - } -} - -static 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; -} - -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); - 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; -} - -static 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_check_ipv6_ready(void) -{ - static int do_get_conf = 0; - static int is_icmp_check_set; - static int is_tcp_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 (is_icmp_check_set == 0) { - tlog(TLOG_INFO, "ICMP ping is disabled, no ipv6 icmp 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; - } - } -} - -static 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); -} - -static 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; -} - -static void _dns_server_request_set_id(struct dns_request *request, unsigned short id) -{ - request->id = id; -} - -static 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); - } - } -} - -static 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; -} - -static 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; -} - -static int _dns_server_process_smartdns_domain(struct dns_request *request) -{ - struct dns_rule_flags *rule_flag = NULL; - unsigned int flags = 0; - - /* get domain rule flag */ - rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); - if (rule_flag == NULL) { - return -1; - } - - if (_dns_server_is_dns_rule_extract_match(request, DOMAIN_RULE_FLAGS) == 0) { - return -1; - } - - flags = rule_flag->flags; - if (!(flags & DOMAIN_FLAG_SMARTDNS_DOMAIN)) { - return -1; - } - - return _dns_server_reply_request_eth_ip(request); -} - -static 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; -} - -static int _dns_server_process_special_query(struct dns_request *request) -{ - int ret = 0; - - switch (request->qtype) { - case DNS_T_PTR: - 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; -} - -static 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; -} - -static 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; - } -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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; - } - - 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; -} - -static 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; -} - -static 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; - if (request->conn) { - group_name = request->conn->dns_group; - } - - 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; -} - -static 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); - - if (_dns_server_process_dns64(request) != 0) { - 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: - request = NULL; - return ret; -} - -static 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; -} - -static 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. - safe_strncpy(request->domain, domain, sizeof(request->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; -} - -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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -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_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); -} - -static 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; -} - -static void _dns_server_client_touch(struct dns_server_conn_head *conn) -{ - time(&conn->last_request_time); -} - -static int _dns_server_client_close(struct dns_server_conn_head *conn) -{ - if (conn->fd > 0) { - _dns_server_epoll_ctl(conn, EPOLL_CTL_DEL, 0); - close(conn->fd); - conn->fd = -1; - } - - list_del_init(&conn->list); - - _dns_server_conn_release(conn); - - return 0; -} - -static 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; -} - -static 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 = malloc(sizeof(*tcpclient)); - if (tcpclient == NULL) { - tlog(TLOG_ERROR, "malloc for tcpclient failed."); - goto errout; - } - memset(tcpclient, 0, sizeof(*tcpclient)); - - tcpclient->head.fd = fd; - tcpclient->head.type = 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; - - atomic_set(&tcpclient->head.refcnt, 0); - 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); - - list_add(&tcpclient->head.list, &server.conn_list); - _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; -} - -static ssize_t _ssl_read(struct dns_server_conn_tls_client *conn, void *buff, int num) -{ - ssize_t ret = 0; - if (conn == NULL || buff == NULL) { - return SSL_ERROR_SYSCALL; - } - - pthread_mutex_lock(&conn->ssl_lock); - 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; - if (conn == NULL || buff == NULL || conn->ssl == NULL) { - return SSL_ERROR_SYSCALL; - } - - pthread_mutex_lock(&conn->ssl_lock); - 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; - if (conn == NULL || conn->ssl == NULL) { - return SSL_ERROR_SYSCALL; - } - - pthread_mutex_lock(&conn->ssl_lock); - 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; - if (conn == NULL || conn->ssl == NULL) { - return SSL_ERROR_SYSCALL; - } - - pthread_mutex_lock(&conn->ssl_lock); - err = SSL_accept(conn->ssl); - pthread_mutex_unlock(&conn->ssl_lock); - return err; -} - -static 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: - tlog(TLOG_DEBUG, "SSL syscall failed, %s", strerror(errno)); - return ret; - default: - errno = EFAULT; - ret = -1; - break; - } - - return ret; -} - -static 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; -} - -static 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) { - tlog(TLOG_ERROR, "epoll ctl failed, %s", strerror(errno)); - goto errout; - } - - return 0; - -errout: - return -1; -} - -static 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; - } -} - -static 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; - } - - 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 (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, tcpclient->recvbuff.size); - if (len < 0) { - if (len == -1) { - ret = 0; - goto out; - } - - 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); - request_len = ntohs(*((unsigned short *)(request_data))); - - if (request_len >= sizeof(tcpclient->recvbuff.buf)) { - tlog(TLOG_DEBUG, "request length is invalid."); - 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; -} - -static 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; - - for (;;) { - 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; -} - -static 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; -} - -static 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; - 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; - } - - tls_client = malloc(sizeof(*tls_client)); - if (tls_client == NULL) { - tlog(TLOG_ERROR, "malloc for tls_client failed."); - goto errout; - } - memset(tls_client, 0, sizeof(*tls_client)); - - tls_client->tcp.head.fd = fd; - if (tls_server->head.type == DNS_CONN_TYPE_TLS_SERVER) { - tls_client->tcp.head.type = DNS_CONN_TYPE_TLS_CLIENT; - } else if (tls_server->head.type == DNS_CONN_TYPE_HTTPS_SERVER) { - tls_client->tcp.head.type = DNS_CONN_TYPE_HTTPS_CLIENT; - } else { - tlog(TLOG_ERROR, "invalid http server type."); - goto errout; - } - 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); - - list_add(&tls_client->tcp.head.list, &server.conn_list); - _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; -} - -static 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; - 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; - } - } - - 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_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_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; -} - -static 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; -} - -static 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; -} - -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; -} - -static 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); - 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); - } -} - -#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_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 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) { - _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); - } -} - -static void _dns_server_close_socket(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) - { - _dns_server_client_close(conn); - } -} - -static 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; - } - } -} - -int dns_server_run(void) -{ - struct epoll_event events[DNS_MAX_EVENTS + 1]; - int num = 0; - int i = 0; - unsigned long now = {0}; - unsigned long last = {0}; - unsigned int msec = 0; - int sleep = 100; - int sleep_time = 0; - unsigned long expect_time = 0; - - sleep_time = sleep; - now = get_tick_count() - sleep; - last = now; - expect_time = now + sleep; - while (atomic_read(&server.run)) { - now = get_tick_count(); - if (sleep_time > 0) { - sleep_time -= now - last; - if (sleep_time <= 0) { - sleep_time = 0; - } - - int cnt = sleep_time / sleep; - msec -= cnt; - expect_time -= cnt * sleep; - sleep_time -= cnt * sleep; - } - - if (now >= expect_time) { - msec++; - if (last != now) { - _dns_server_period_run(msec); - } - sleep_time = sleep - (now - expect_time); - if (sleep_time < 0) { - sleep_time = 0; - expect_time = now; - } - - /* 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)) { - int cnt = 10 - (msec % 10) - 1; - sleep_time += sleep * cnt; - msec += cnt; - /* sleep to next second */ - expect_time += sleep * cnt; - } - pthread_mutex_unlock(&server.request_list_lock); - expect_time += sleep; - } - last = now; - - 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; -} - -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_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_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; -} - -static 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; -} - -static 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; - conn = malloc(sizeof(struct dns_server_conn_udp)); - if (conn == NULL) { - goto errout; - } - INIT_LIST_HEAD(&conn->head.list); - - fd = _dns_create_socket(host_ip, SOCK_DGRAM); - if (fd <= 0) { - goto errout; - } - - conn->head.type = DNS_CONN_TYPE_UDP_SERVER; - conn->head.fd = fd; - _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; -} - -static 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; - conn = malloc(sizeof(struct dns_server_conn_tcp_server)); - if (conn == NULL) { - goto errout; - } - INIT_LIST_HEAD(&conn->head.list); - - fd = _dns_create_socket(host_ip, SOCK_STREAM); - if (fd <= 0) { - goto errout; - } - - setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &on, sizeof(on)); - - conn->head.type = DNS_CONN_TYPE_TCP_SERVER; - conn->head.fd = fd; - _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; -} - -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); -} - -static 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; - } - - conn = malloc(sizeof(struct dns_server_conn_tls_server)); - if (conn == NULL) { - goto errout; - } - INIT_LIST_HEAD(&conn->head.list); - - 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; - } - - conn->head.type = conn_type; - conn->head.fd = fd; - 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; -} - -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; -} - -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_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; - } - - if (dns_conf.audit_syslog) { - tlog_reg_output_func(dns_audit, _dns_server_audit_syslog); - } - - 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; -} - -static 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); -} - -static 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_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; -} - -static 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; -} - -static int _dns_server_local_addr_cache_init(void) -{ - int fd = 0; - 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; -} - -static 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; -} - -static 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; -} - -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_attr_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_attr_init(&attr); - 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); - INIT_LIST_HEAD(&server.request_list); - - 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; -} diff --git a/src/dns_server/address.c b/src/dns_server/address.c new file mode 100755 index 0000000000..ca1d800052 --- /dev/null +++ b/src/dns_server/address.c @@ -0,0 +1,325 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(sizeof(*addr_map)); + if (addr_map == NULL) { + pthread_mutex_unlock(&request->ip_map_lock); + tlog(TLOG_ERROR, "malloc addr map failed"); + return -1; + } + memset(addr_map, 0, sizeof(*addr_map)); + + 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); + request->ip_ttl = request->conf->dns_rr_ttl_min > 0 ? request->conf->dns_rr_ttl_min : DNS_SERVER_TMOUT_TTL; + 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); + request->ip_ttl = request->conf->dns_rr_ttl_min > 0 ? request->conf->dns_rr_ttl_min : DNS_SERVER_TMOUT_TTL; + 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; +} diff --git a/src/dns_server/address.h b/src/dns_server/address.h new file mode 100755 index 0000000000..3a2ddb036d --- /dev/null +++ b/src/dns_server/address.h @@ -0,0 +1,44 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/answer.c b/src/dns_server/answer.c new file mode 100755 index 0000000000..5e8232275d --- /dev/null +++ b/src/dns_server/answer.c @@ -0,0 +1,522 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_https_param *p = NULL; + int priority = 0; + struct dns_request_https *https_svcb; + int no_ipv4 = 0; + int no_ipv6 = 0; + 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) { + no_ipv4 = 1; + } + + if (https_record_rule->filter.no_ipv6hint) { + no_ipv6 = 1; + } + } + + ret = dns_get_HTTPS_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 = request->https_svcb; + if (https_svcb == 0) { + /* ignore non-matched query type */ + tlog(TLOG_WARN, "https svcb not set"); + return -1; + } + + 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_get_HTTPS_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 (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; + } + } + + _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; + } + + /* when QTYPE is HTTPS, check if support */ + if (request->qtype == DNS_T_HTTPS) { + int https_svcb_record_num = 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_HTTPS: { + https_svcb_record_num++; + if (https_svcb_record_num <= 1) { + continue; + } + + /* CURRENT NOT SUPPORT MUTI HTTPS RECORD */ + *need_passthrouh = 1; + return DNS_CLIENT_ACTION_OK; + } + } + } + } + } + + 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; + if (request->has_ip == 0) { + request->passthrough = 1; + _dns_server_request_complete(request); + } + } 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; + } + + 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; +} diff --git a/src/dns_server/answer.h b/src/dns_server/answer.h new file mode 100755 index 0000000000..481da64555 --- /dev/null +++ b/src/dns_server/answer.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/audit.c b/src/dns_server/audit.c new file mode 100755 index 0000000000..a29c7a28b4 --- /dev/null +++ b/src/dns_server/audit.c @@ -0,0 +1,192 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 + +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; + } + + if (request->conn == NULL) { + 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; +} + +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; + } + + if (dns_conf.audit_syslog) { + tlog_reg_output_func(dns_audit, _dns_server_audit_syslog); + } + + 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; +} \ No newline at end of file diff --git a/src/dns_server/audit.h b/src/dns_server/audit.h new file mode 100755 index 0000000000..7dd4a446c9 --- /dev/null +++ b/src/dns_server/audit.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/cache.c b/src/dns_server/cache.c new file mode 100755 index 0000000000..8d9cfa7ac7 --- /dev/null +++ b/src/dns_server/cache.c @@ -0,0 +1,707 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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: + 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((struct dns_cache_data *)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((struct dns_cache_data *)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); + 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; +} diff --git a/src/dns_server/cache.h b/src/dns_server/cache.h new file mode 100755 index 0000000000..ea15c17cf3 --- /dev/null +++ b/src/dns_server/cache.h @@ -0,0 +1,52 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/client_rule.c b/src/dns_server/client_rule.c new file mode 100755 index 0000000000..b15fc0351b --- /dev/null +++ b/src/dns_server/client_rule.c @@ -0,0 +1,51 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/dns_server/client_rule.h b/src/dns_server/client_rule.h new file mode 100755 index 0000000000..176652b762 --- /dev/null +++ b/src/dns_server/client_rule.h @@ -0,0 +1,33 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/cname.c b/src/dns_server/cname.c new file mode 100755 index 0000000000..fc93a2b637 --- /dev/null +++ b/src/dns_server/cname.c @@ -0,0 +1,184 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_rule_flags *rule_flag = NULL; + struct dns_request_domain_rule domain_rule; + + 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; + } + + 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; +} diff --git a/src/dns_server/cname.h b/src/dns_server/cname.h new file mode 100755 index 0000000000..b1a7e0633b --- /dev/null +++ b/src/dns_server/cname.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/connection.c b/src/dns_server/connection.c new file mode 100755 index 0000000000..db73cf5ac1 --- /dev/null +++ b/src/dns_server/connection.c @@ -0,0 +1,198 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 +#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; + } + 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; + } + } + + 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; + + list_for_each_entry_safe(conn, tmp, &server.conn_list, list) + { + _dns_server_client_close(conn); + } +} + +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); + + _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; +} diff --git a/src/dns_server/connection.h b/src/dns_server/connection.h new file mode 100755 index 0000000000..665add1d94 --- /dev/null +++ b/src/dns_server/connection.h @@ -0,0 +1,51 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/context.c b/src/dns_server/context.c new file mode 100755 index 0000000000..c79f06ca3b --- /dev/null +++ b/src/dns_server/context.c @@ -0,0 +1,1008 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "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_srv_records *srv_records = request->srv_records; + struct dns_srv_record *srv_record = NULL; + int ret = 0; + + if (srv_records == NULL) { + return 0; + } + + list_for_each_entry(srv_record, &srv_records->list, list) + { + ret = dns_add_SRV(context->packet, DNS_RRS_AN, request->domain, request->ip_ttl, srv_record->priority, + srv_record->weight, srv_record->port, srv_record->host); + if (ret != 0) { + return -1; + } + } + + return 0; +} + +static int _dns_add_rrs_HTTPS(struct dns_server_post_context *context) +{ + struct dns_request *request = context->request; + struct dns_request_https *https_svcb = request->https_svcb; + int ret = 0; + struct dns_rr_nested param; + + if (https_svcb == NULL || request->qtype != DNS_T_HTTPS) { + return 0; + } + + 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 (request->has_ip) { + unsigned char *addr[1]; + addr[0] = request->ip_addr; + if (request->ip_addr_type == DNS_T_A) { + ret = dns_HTTPS_add_ipv4hint(¶m, addr, 1); + } + } + + 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 (request->has_ip) { + unsigned char *addr[1]; + addr[0] = request->ip_addr; + if (request->ip_addr_type == DNS_T_AAAA) { + ret = dns_HTTPS_add_ipv6hint(¶m, addr, 1); + } + } + + 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 (request->https_svcb != NULL) { + 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 (request->srv_records != NULL) { + 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_https_param *p = NULL; + int priority = 0; + + int ret = dns_get_HTTPS_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_get_HTTPS_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(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; + 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(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; + } + + 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(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); +} diff --git a/src/dns_server/context.h b/src/dns_server/context.h new file mode 100755 index 0000000000..04310c817b --- /dev/null +++ b/src/dns_server/context.h @@ -0,0 +1,43 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/dns_server.c b/src/dns_server/dns_server.c new file mode 100755 index 0000000000..d1b9ef666f --- /dev/null +++ b/src/dns_server/dns_server.c @@ -0,0 +1,978 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "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 { + 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; + context.no_check_add_ip = 1; + _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); + + if (_dns_server_process_dns64(request) != 0) { + 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: + request = NULL; + 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) { + _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 long last = {0}; + unsigned int msec = 0; + int sleep = 100; + int sleep_time = 0; + unsigned long expect_time = 0; + + sleep_time = sleep; + now = get_tick_count() - sleep; + last = now; + expect_time = now + sleep; + while (atomic_read(&server.run)) { + now = get_tick_count(); + if (sleep_time > 0) { + sleep_time -= now - last; + if (sleep_time <= 0) { + sleep_time = 0; + } + + int cnt = sleep_time / sleep; + msec -= cnt; + expect_time -= cnt * sleep; + sleep_time -= cnt * sleep; + } + + if (now >= expect_time) { + msec++; + if (last != now) { + _dns_server_period_run(msec); + } + sleep_time = sleep - (now - expect_time); + if (sleep_time < 0) { + sleep_time = 0; + expect_time = now; + } + + /* 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)) { + int cnt = 10 - (msec % 10) - 1; + sleep_time += sleep * cnt; + msec += cnt; + /* sleep to next second */ + expect_time += sleep * cnt; + } + pthread_mutex_unlock(&server.request_list_lock); + expect_time += sleep; + } + last = now; + + 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; +} diff --git a/src/dns_server/dns_server.h b/src/dns_server/dns_server.h new file mode 100755 index 0000000000..cb359db6db --- /dev/null +++ b/src/dns_server/dns_server.h @@ -0,0 +1,417 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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/tlog.h" +#include "smartdns/util.h" +#include "smartdns/regexp.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 (250) +#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_MAX_NUM (1024 * 8) +#define DNS_SERVER_NEIGHBOR_CACHE_TIMEOUT (3600 * 1) +#define DNS_SERVER_NEIGHBOR_CACHE_NOMAC_TIMEOUT 60 + +#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; + +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; + unsigned char *key[DOMAIN_RULE_MAX]; + uint32_t key_len[DOMAIN_RULE_MAX]; +int match; +}; + +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; +}; + +/* 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 { + 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 { + 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; +}; + +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]; + 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 dns_request_https *https_svcb; + + 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 dns_srv_records *srv_records; + + 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 */ + 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 diff --git a/src/dns_server/dualstack.c b/src/dns_server/dualstack.c new file mode 100755 index 0000000000..be2da4197c --- /dev/null +++ b/src/dns_server/dualstack.c @@ -0,0 +1,269 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; + + 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 (is_icmp_check_set == 0) { + tlog(TLOG_INFO, "ICMP ping is disabled, no ipv6 icmp 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; + } + } +} + +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; +} diff --git a/src/dns_server/dualstack.h b/src/dns_server/dualstack.h new file mode 100755 index 0000000000..7e2297ad77 --- /dev/null +++ b/src/dns_server/dualstack.h @@ -0,0 +1,39 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/ip_rule.c b/src/dns_server/ip_rule.c new file mode 100755 index 0000000000..dc5b8e7343 --- /dev/null +++ b/src/dns_server/ip_rule.c @@ -0,0 +1,209 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "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) { + 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; +} diff --git a/src/dns_server/ip_rule.h b/src/dns_server/ip_rule.h new file mode 100755 index 0000000000..38c76c1b5b --- /dev/null +++ b/src/dns_server/ip_rule.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/ipset_nftset.c b/src/dns_server/ipset_nftset.c new file mode 100755 index 0000000000..3b6af37437 --- /dev/null +++ b/src/dns_server/ipset_nftset.c @@ -0,0 +1,91 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} \ No newline at end of file diff --git a/src/dns_server/ipset_nftset.h b/src/dns_server/ipset_nftset.h new file mode 100755 index 0000000000..716d53f13e --- /dev/null +++ b/src/dns_server/ipset_nftset.h @@ -0,0 +1,36 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/local_addr.c b/src/dns_server/local_addr.c new file mode 100755 index 0000000000..f0a404cb2b --- /dev/null +++ b/src/dns_server/local_addr.c @@ -0,0 +1,246 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(sizeof(struct local_addr_cache_item)); + if (addr_cache_item == NULL) { + return; + } + memset(addr_cache_item, 0, sizeof(struct local_addr_cache_item)); + } 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 = 0; + 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; +} diff --git a/src/dns_server/local_addr.h b/src/dns_server/local_addr.h new file mode 100755 index 0000000000..ee1f2da84f --- /dev/null +++ b/src/dns_server/local_addr.h @@ -0,0 +1,37 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/mdns.c b/src/dns_server/mdns.c new file mode 100755 index 0000000000..fbe115843f --- /dev/null +++ b/src/dns_server/mdns.c @@ -0,0 +1,86 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} \ No newline at end of file diff --git a/src/dns_server/mdns.h b/src/dns_server/mdns.h new file mode 100755 index 0000000000..9eb8df9d58 --- /dev/null +++ b/src/dns_server/mdns.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/neighbor.c b/src/dns_server/neighbor.c new file mode 100755 index 0000000000..be65c1c1c1 --- /dev/null +++ b/src/dns_server/neighbor.c @@ -0,0 +1,275 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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, *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 = malloc(sizeof(*item)); + memset(item, 0, 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 < 2; i++) { + ret = netlink_get_neighbors(family, _dns_server_neighbors_callback, &args); + if (ret < 0) { + goto add_cache; + } + + if (ret != 1) { + /* FIXME: ugly force refresh NDP table by sending ICMP message.*/ + if (i == 0) { + char host[DNS_MAX_CNAME_LEN] = {0}; + if (family == AF_INET) { + snprintf(host, sizeof(host), "%d.%d.%d.%d", netaddr[0], netaddr[1], netaddr[2], netaddr[3]); + } else if (family == AF_INET6) { + snprintf(host, sizeof(host), + "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", netaddr[0], + netaddr[1], netaddr[2], netaddr[3], netaddr[4], netaddr[5], netaddr[6], netaddr[7], + netaddr[8], netaddr[9], netaddr[10], netaddr[11], netaddr[12], netaddr[13], netaddr[14], + netaddr[15]); + } + struct ping_host_struct *ping_host = fast_ping_start(PING_TYPE_ICMP, host, 0, 10, 1000, NULL, NULL); + if (ping_host) { + /* wait for NDP*/ + usleep(1000); + fast_ping_stop(ping_host); + continue; + } + } + + 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); + return NULL; +} diff --git a/src/dns_server/neighbor.h b/src/dns_server/neighbor.h new file mode 100755 index 0000000000..539e6c8a65 --- /dev/null +++ b/src/dns_server/neighbor.h @@ -0,0 +1,39 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/prefetch.c b/src/dns_server/prefetch.c new file mode 100755 index 0000000000..ba8f8b181b --- /dev/null +++ b/src/dns_server/prefetch.c @@ -0,0 +1,111 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/dns_server/prefetch.h b/src/dns_server/prefetch.h new file mode 100755 index 0000000000..e7f72ee1c4 --- /dev/null +++ b/src/dns_server/prefetch.h @@ -0,0 +1,39 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/ptr.c b/src/dns_server/ptr.c new file mode 100755 index 0000000000..b54ae0b3b3 --- /dev/null +++ b/src/dns_server/ptr.c @@ -0,0 +1,351 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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, "%hhd.%hhd.%hhd.%hhd.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; +} diff --git a/src/dns_server/ptr.h b/src/dns_server/ptr.h new file mode 100755 index 0000000000..a5082a1c2e --- /dev/null +++ b/src/dns_server/ptr.h @@ -0,0 +1,43 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/request.c b/src/dns_server/request.c new file mode 100755 index 0000000000..dad896bc4d --- /dev/null +++ b/src/dns_server/request.c @@ -0,0 +1,1431 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "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; +} + +int _dns_server_is_dns64_request(struct dns_request *request) +{ + if (request->qtype != DNS_T_AAAA) { + return 0; + } + + if (request->dualstack_selection_query == 1) { + return 0; + } + + if (request->conf->dns_dns64.prefix_len <= 0) { + 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 || request->rcode == DNS_RC_NXDOMAIN) { + 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); + if (request->https_svcb) { + free(request->https_svcb); + } + 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; + } + + 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 = malloc(sizeof(*request)); + if (request == NULL) { + tlog(TLOG_ERROR, "malloc request failed.\n"); + goto errout; + } + + memset(request, 0, sizeof(*request)); + 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); + 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; +} + +int _dns_server_process_DDR(struct dns_request *request) +{ + return _dns_server_reply_SOA(DNS_RC_NOERROR, request); +} + +int _dns_server_process_srv(struct dns_request *request) +{ + struct dns_srv_records *srv_records = dns_server_get_srv_record(request->domain); + if (srv_records == NULL) { + return -1; + } + + request->rcode = DNS_RC_NOERROR; + request->ip_ttl = _dns_server_get_local_ttl(request); + request->srv_records = srv_records; + + 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; +} + +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 = malloc(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; + } + memset(new_addr_map, 0, sizeof(struct dns_ip_address)); + + 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); + + 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; +} + +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); + + if (request->qtype != DNS_T_HTTPS) { + return 0; + } + + if (request->https_svcb != NULL) { + return 0; + } + + request->https_svcb = malloc(sizeof(*request->https_svcb)); + if (request->https_svcb == NULL) { + return -1; + } + memset(request->https_svcb, 0, sizeof(*request->https_svcb)); + + if (https_record_rule == NULL) { + return 0; + } + + if (https_record_rule->record.enable == 0) { + return 0; + } + + safe_strncpy(request->https_svcb->domain, request->domain, sizeof(request->https_svcb->domain)); + safe_strncpy(request->https_svcb->target, https_record_rule->record.target, sizeof(request->https_svcb->target)); + request->https_svcb->priority = https_record_rule->record.priority; + request->https_svcb->port = https_record_rule->record.port; + memcpy(request->https_svcb->ech, https_record_rule->record.ech, https_record_rule->record.ech_len); + request->https_svcb->ech_len = https_record_rule->record.ech_len; + memcpy(request->https_svcb->alpn, https_record_rule->record.alpn, sizeof(request->https_svcb->alpn)); + request->https_svcb->alpn_len = https_record_rule->record.alpn_len; + if (https_record_rule->record.has_ipv4) { + memcpy(request->ip_addr, https_record_rule->record.ipv4_addr, DNS_RR_A_LEN); + request->ip_addr_type = DNS_T_A; + request->has_ip = 1; + } else if (https_record_rule->record.has_ipv6) { + memcpy(request->ip_addr, https_record_rule->record.ipv6_addr, DNS_RR_AAAA_LEN); + request->ip_addr_type = DNS_T_AAAA; + request->has_ip = 1; + } + + request->rcode = DNS_RC_NOERROR; + + return -1; +} + +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) +{ + struct dns_rule_flags *rule_flag = NULL; + unsigned int flags = 0; + + /* get domain rule flag */ + rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); + if (rule_flag == NULL) { + return -1; + } + + if (_dns_server_is_dns_rule_extract_match(request, DOMAIN_RULE_FLAGS) == 0) { + return -1; + } + + flags = rule_flag->flags; + 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_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. + safe_strncpy(request->domain, domain, sizeof(request->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; +} diff --git a/src/dns_server/request.h b/src/dns_server/request.h new file mode 100755 index 0000000000..08a7585973 --- /dev/null +++ b/src/dns_server/request.h @@ -0,0 +1,115 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); + +int _dns_server_process_DDR(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_DDR(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_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 diff --git a/src/dns_server/request_pending.c b/src/dns_server/request_pending.c new file mode 100755 index 0000000000..989a9af62d --- /dev/null +++ b/src/dns_server/request_pending.c @@ -0,0 +1,145 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(sizeof(*pending_list)); + if (pending_list == NULL) { + ret = -1; + goto out; + } + + memset(pending_list, 0, sizeof(*pending_list)); + 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; + + if (request->request_pending_list == NULL) { + return 0; + } + + pthread_mutex_lock(&server.request_pending_lock); + 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; +} diff --git a/src/dns_server/request_pending.h b/src/dns_server/request_pending.h new file mode 100755 index 0000000000..96cfe3e56c --- /dev/null +++ b/src/dns_server/request_pending.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/rules.c b/src/dns_server/rules.c new file mode 100755 index 0000000000..76283017a6 --- /dev/null +++ b/src/dns_server/rules.c @@ -0,0 +1,648 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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]; + 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) +{ + struct dns_rule_flags *rule_flag = (struct dns_rule_flags *)request_domain_rule->rules[0]; + unsigned int flags = 0; + + if (rule_flag == NULL) { + return; + } + flags = rule_flag->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; + } + + if (domain_rule->sub_rule_only != domain_rule->root_rule_only) { + /* only subkey rule */ + if (domain_rule->sub_rule_only == 1 && is_subkey == 0) { + return 0; + } + + /* only root key rule */ + if (domain_rule->root_rule_only == 1 && is_subkey == 1) { + return 0; + } + } + + if (walk_args->rule_index >= 0) { + i = walk_args->rule_index; + } else { + i = 0; + } + + for (; i < DOMAIN_RULE_MAX; i++) { + if (domain_rule->rules[i] == NULL) { + if (walk_args->rule_index >= 0) { + break; + } + 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; + } +walk_args->match = 1; + } + + /* 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]; + struct rule_walk_args walk_args; + int matched_key_len = DNS_MAX_CNAME_LEN; + unsigned char matched_key[DNS_MAX_CNAME_LEN]; + int i = 0; +char regexp[DNS_MAX_REGEXP_LEN]; + + 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; + + /* find domain rule */ + art_substring_walk(&conf->domain_rule.tree, (unsigned char *)domain_key, domain_len, _dns_server_get_rules, + &walk_args); + +if (!walk_args.match && has_regexp()) { + memset(&walk_args, 0, sizeof(walk_args)); + walk_args.args = request_domain_rule; + + memset(regexp,0,sizeof(regexp)); + if (dns_regexp_match(domain, regexp) == 0) { + /* reverse regexp string */ + domain_len = strlen(regexp); + reverse_string(domain_key, regexp, domain_len, 1); + domain_key[domain_len] = '.'; + domain_len++; + domain_key[domain_len] = 0; + + /* find domain rule with regexp */ + 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]; + 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; + default: + if (ttl == 0) { + /* Get TTL */ + char tmpname[DNS_MAX_CNAME_LEN]; + char tmpbuf[DNS_MAX_CNAME_LEN]; + 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) { + 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); +} + +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) +{ + struct dns_rule_flags *rule_flag = NULL; + unsigned int flags = 0; + int rcode = DNS_RC_NOERROR; + + /* get domain rule flag */ + rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); + if (rule_flag != NULL) { + flags = rule_flag->flags; + } + + 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; +} \ No newline at end of file diff --git a/src/dns_server/rules.h b/src/dns_server/rules.h new file mode 100755 index 0000000000..e1c365bea3 --- /dev/null +++ b/src/dns_server/rules.h @@ -0,0 +1,56 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); + +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 diff --git a/src/dns_server/server_https.c b/src/dns_server/server_https.c new file mode 100755 index 0000000000..fb923146f9 --- /dev/null +++ b/src/dns_server/server_https.c @@ -0,0 +1,98 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 +#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; +} \ No newline at end of file diff --git a/src/dns_server/server_https.h b/src/dns_server/server_https.h new file mode 100755 index 0000000000..3a51624de9 --- /dev/null +++ b/src/dns_server/server_https.h @@ -0,0 +1,36 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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" + +#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 diff --git a/src/dns_server/server_socket.c b/src/dns_server/server_socket.c new file mode 100755 index 0000000000..e5020d114a --- /dev/null +++ b/src/dns_server/server_socket.c @@ -0,0 +1,169 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/dns_server/server_socket.h b/src/dns_server/server_socket.h new file mode 100755 index 0000000000..ce1dc70d4b --- /dev/null +++ b/src/dns_server/server_socket.h @@ -0,0 +1,31 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/server_tcp.c b/src/dns_server/server_tcp.c new file mode 100755 index 0000000000..5b7c692395 --- /dev/null +++ b/src/dns_server/server_tcp.c @@ -0,0 +1,580 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(sizeof(*tcpclient)); + if (tcpclient == NULL) { + tlog(TLOG_ERROR, "malloc for tcpclient failed."); + goto errout; + } + memset(tcpclient, 0, sizeof(*tcpclient)); + _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); + request_len = ntohs(*((unsigned short *)(request_data))); + + 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 = malloc(sizeof(struct dns_server_conn_tcp_server)); + if (conn == NULL) { + goto errout; + } + memset(conn, 0, sizeof(struct dns_server_conn_tcp_server)); + _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; +} diff --git a/src/dns_server/server_tcp.h b/src/dns_server/server_tcp.h new file mode 100755 index 0000000000..6fff6ac22b --- /dev/null +++ b/src/dns_server/server_tcp.h @@ -0,0 +1,51 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/server_tls.c b/src/dns_server/server_tls.c new file mode 100755 index 0000000000..8ac4310f19 --- /dev/null +++ b/src/dns_server/server_tls.c @@ -0,0 +1,482 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_socket.h" +#include "server_tcp.h" + +#include +#include +#include +#include +#include +#include +#include + +static ssize_t _ssl_read(struct dns_server_conn_tls_client *conn, void *buff, int num) +{ + ssize_t ret = 0; + if (conn == NULL || buff == NULL) { + return SSL_ERROR_SYSCALL; + } + + pthread_mutex_lock(&conn->ssl_lock); + 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; + if (conn == NULL || buff == NULL || conn->ssl == NULL) { + return SSL_ERROR_SYSCALL; + } + + pthread_mutex_lock(&conn->ssl_lock); + 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; + if (conn == NULL || conn->ssl == NULL) { + return SSL_ERROR_SYSCALL; + } + + pthread_mutex_lock(&conn->ssl_lock); + 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; + if (conn == NULL || conn->ssl == NULL) { + return SSL_ERROR_SYSCALL; + } + + pthread_mutex_lock(&conn->ssl_lock); + 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: + 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 = malloc(sizeof(*tls_client)); + if (tls_client == NULL) { + tlog(TLOG_ERROR, "malloc for tls_client failed."); + goto errout; + } + memset(tls_client, 0, sizeof(*tls_client)); + _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; + 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; + } + } + + 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; + } + + conn = malloc(sizeof(struct dns_server_conn_tls_server)); + if (conn == NULL) { + goto errout; + } + memset(conn, 0, sizeof(struct dns_server_conn_tls_server)); + _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; +} diff --git a/src/dns_server/server_tls.h b/src/dns_server/server_tls.h new file mode 100755 index 0000000000..f89ba3cbee --- /dev/null +++ b/src/dns_server/server_tls.h @@ -0,0 +1,45 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/server_udp.c b/src/dns_server/server_udp.c new file mode 100755 index 0000000000..c44adff758 --- /dev/null +++ b/src/dns_server/server_udp.c @@ -0,0 +1,197 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(sizeof(struct dns_server_conn_udp)); + if (conn == NULL) { + goto errout; + } + memset(conn, 0, sizeof(struct dns_server_conn_udp)); + + _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; +} diff --git a/src/dns_server/server_udp.h b/src/dns_server/server_udp.h new file mode 100755 index 0000000000..e363ba0d4b --- /dev/null +++ b/src/dns_server/server_udp.h @@ -0,0 +1,40 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_server/soa.c b/src/dns_server/soa.c new file mode 100755 index 0000000000..b1ad25a37c --- /dev/null +++ b/src/dns_server/soa.c @@ -0,0 +1,172 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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) +{ + struct dns_rule_flags *rule_flag = NULL; + unsigned int flags = 0; + + 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; + } + + rule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS); + if (rule_flag) { + flags = rule_flag->flags; + if (flags & DOMAIN_FLAG_ADDR_SOA) { + stats_inc(&dns_stats.request.blocked_count); + return 1; + } + + if (flags & DOMAIN_FLAG_ADDR_IGN) { + request->skip_qtype_soa = 1; + return 0; + } + + 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; + } + } else if (qtype == DNS_T_HTTPS) { + if (request->domain_rule.rules[DOMAIN_RULE_HTTPS] == NULL) { + return 1; + } + } + + return 0; +} + +int _dns_server_reply_SOA(int rcode, struct dns_request *request) +{ + /* return SOA record */ + request->rcode = rcode; + if (request->ip_ttl <= 0) { + request->ip_ttl = DNS_SERVER_SOA_TTL; + } + + _dns_server_setup_soa(request); + + 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_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; +} diff --git a/src/dns_server/soa.h b/src/dns_server/soa.h new file mode 100755 index 0000000000..baa48be566 --- /dev/null +++ b/src/dns_server/soa.h @@ -0,0 +1,41 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_qtype_soa(struct dns_request *request); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/dns_server/speed_check.c b/src/dns_server/speed_check.c new file mode 100755 index 0000000000..7e4f2b8b34 --- /dev/null +++ b/src/dns_server/speed_check.c @@ -0,0 +1,281 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = request->ping_time; + + if (request == NULL) { + return; + } + + 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 + 12, 4); + 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); + if (ping_timeout > DNS_PING_TIMEOUT) { + ping_timeout = DNS_PING_TIMEOUT; + } else 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; + 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; +} diff --git a/src/dns_server/speed_check.h b/src/dns_server/speed_check.h new file mode 100755 index 0000000000..60aa19de76 --- /dev/null +++ b/src/dns_server/speed_check.h @@ -0,0 +1,34 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/dns_stats.c b/src/dns_stats.c old mode 100644 new mode 100755 index 5b8a8ec86e..19da7312c2 --- a/src/dns_stats.c +++ b/src/dns_stats.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,9 +16,9 @@ * along with this program. If not, see . */ -#include "dns_stats.h" -#include "stddef.h" -#include "string.h" +#include "smartdns/dns_stats.h" +#include +#include struct dns_stats dns_stats; diff --git a/src/fast_ping.c b/src/fast_ping.c deleted file mode 100644 index b50a7d8828..0000000000 --- a/src/fast_ping.c +++ /dev/null @@ -1,2381 +0,0 @@ -/************************************************************************* - * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . - * - * smartdns is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * smartdns is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 "fast_ping.h" -#include "atomic.h" -#include "hashtable.h" -#include "list.h" -#include "tlog.h" -#include "util.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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_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 ss_family; - union { - struct sockaddr addr; - struct sockaddr_in6 in6; - struct sockaddr_in in; - }; - socklen_t addr_len; - struct fast_ping_packet packet; - /* for memory address alignment */ - 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 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; -}; - -static int is_fast_ping_init; -static struct fast_ping_struct ping; -static atomic_t ping_sid = ATOMIC_INIT(0); -static int bool_print_log = 1; - -static void _fast_ping_host_put(struct ping_host_struct *ping_host); -static 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); -static int _fast_ping_create_icmp(FAST_PING_TYPE type); - -static void _fast_ping_wakeup_thread(void) -{ - uint64_t u = 1; - int unused __attribute__((unused)); - unused = write(ping.event_fd, &u, sizeof(u)); -} - -static 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))); -} - -static 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))) { - perror("WARNING: failed to install socket filter\n"); - } -} - -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))) { - perror("WARNING: failed to install socket filter\n"); - } -} - -static int _fast_ping_sockaddr_ip_cmp(struct sockaddr *first_addr, socklen_t first_addr_len, - struct sockaddr *second_addr, socklen_t second_addr_len) -{ - if (first_addr_len != second_addr_len) { - return -1; - } - - if (first_addr->sa_family != second_addr->sa_family) { - return -1; - } - - switch (first_addr->sa_family) { - case AF_INET: { - struct sockaddr_in *first_addr_in = (struct sockaddr_in *)first_addr; - struct sockaddr_in *second_addr_in = (struct sockaddr_in *)second_addr; - if (memcmp(&first_addr_in->sin_addr.s_addr, &second_addr_in->sin_addr.s_addr, IPV4_ADDR_LEN) != 0) { - return -1; - } - } break; - case AF_INET6: { - struct sockaddr_in6 *first_addr_in6 = (struct sockaddr_in6 *)first_addr; - struct sockaddr_in6 *second_addr_in6 = (struct sockaddr_in6 *)second_addr; - if (memcmp(&first_addr_in6->sin6_addr.s6_addr, &second_addr_in6->sin6_addr.s6_addr, IPV4_ADDR_LEN) != 0) { - return -1; - } - } break; - default: - return -1; - } - - return 0; -} - -static 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; -} - -static 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; -} - -static 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 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) { - tlog(TLOG_ERROR, "invalid refcount of fake ping %s", fake->host); - abort(); - } - return; - } - - pthread_mutex_lock(&ping.map_lock); - if (hash_hashed(&fake->node)) { - hash_del(&fake->node); - } - pthread_mutex_unlock(&ping.map_lock); - - free(fake); -} - -static 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); -} - -static void _fast_ping_fake_get(struct fast_ping_fake_ip *fake) -{ - atomic_inc(&fake->ref); -} - -static 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 = malloc(sizeof(*fake)); - if (fake == NULL) { - goto errout; - } - memset(fake, 0, sizeof(*fake)); - - 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; -} - -static void _fast_ping_host_get(struct ping_host_struct *ping_host) -{ - if (atomic_inc_return(&ping_host->ref) <= 0) { - tlog(TLOG_ERROR, "BUG: ping host ref is invalid, host: %s", ping_host->host); - abort(); - } -} - -static 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; -} - -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); -} - -static 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 = malloc(sizeof(struct fast_ping_notify_event)); - if (notify_event == NULL) { - goto errout; - } - memset(notify_event, 0, sizeof(struct fast_ping_notify_event)); - 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_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) { - tlog(TLOG_ERROR, "invalid refcount of ping_host %s", ping_host->host); - abort(); - } - 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); -} - -static 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); -} - -static 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; -} - -static 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; - if (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL || errno == EHOSTUNREACH) { - goto errout; - } - - if (is_private_addr_sockaddr(&ping_host->addr, ping_host->addr_len)) { - goto errout; - } - - if (errno == EACCES || errno == EPERM) { - if (bool_print_log == 0) { - goto errout; - } - bool_print_log = 0; - } - - 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_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; -} - -static 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 == 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_sendping_udp(struct ping_host_struct *ping_host) -{ - struct ping_dns_head dns_head; - int len = 0; - int flag = 0; - int fd = 0; - - 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_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_ERROR, "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; -} - -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_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 int _fast_ping_create_icmp_sock(FAST_PING_TYPE type) -{ - int fd = -1; - struct ping_host_struct *icmp_host = NULL; - struct epoll_event event; - int buffsize = 64 * 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)); - - 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 = 0; - 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; -} - -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 = 0; - 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; -} - -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); - } -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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_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; - } - - ret = _fast_ping_get_addr_by_type(type, ip_str, port, &gai, &ping_type); - if (ret != 0) { - goto errout; - } - - ping_host = malloc(sizeof(*ping_host)); - if (ping_host == NULL) { - goto errout; - } - - memset(ping_host, 0, sizeof(*ping_host)); - 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; -} - -static 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 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)); - } - } - - 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; -} - -static 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; - } - - if ((uintptr_t)(packet_data + hlen) % __alignof__(void *) == 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -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_UDP6: - case FAST_PING_UDP: - ret = _fast_ping_process_udp(ping_host, now); - break; - default: - tlog(TLOG_ERROR, "BUG: type error : %p, %d, %s, %d", ping_host, ping_host->sid, ping_host->host, ping_host->fd); - abort(); - break; - } - - return ret; -} - -static 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); - } -} - -static 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); - } -} - -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(&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(&ping_host->action_list); - _fast_ping_host_put(ping_host); - continue; - } - ping_host->count--; - } - - _fast_ping_sendping(ping_host); - list_del(&ping_host->action_list); - _fast_ping_host_put(ping_host); - } -} - -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); -} - -static 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; -} - -static 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); - } -} - -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}; - unsigned long last = {0}; - struct timeval tvnow = {0}; - int sleep = 100; - int sleep_time = 0; - unsigned long expect_time = 0; - - setpriority(PRIO_PROCESS, 0, -5); - - sleep_time = sleep; - now = get_tick_count() - sleep; - last = now; - expect_time = now + sleep; - while (atomic_read(&ping.run)) { - now = get_tick_count(); - if (sleep_time > 0) { - sleep_time -= now - last; - if (sleep_time <= 0) { - sleep_time = 0; - } - } - - if (now >= expect_time) { - if (last != now) { - _fast_ping_period_run(); - } - sleep_time = sleep - (now - expect_time); - if (sleep_time < 0) { - sleep_time = 0; - expect_time = now; - } - expect_time += sleep; - } - last = now; - - 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) { - expect_time = get_tick_count(); - } - - if (num == 0) { - continue; - } - - gettimeofday(&tvnow, NULL); - for (i = 0; i < num; i++) { - struct epoll_event *event = &events[i]; - /* read event */ - if (event->data.fd == ping.event_fd) { - 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; -} - -static 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.fd = fdevent; - 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; -} - -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) -{ - if (ping.fd_icmp > 0) { - close(ping.fd_icmp); - ping.fd_icmp = -1; - } - - if (ping.fd_icmp6 > 0) { - close(ping.fd_icmp6); - ping.fd_icmp6 = -1; - } - - if (ping.fd_udp > 0) { - close(ping.fd_udp); - ping.fd_udp = -1; - } - - if (ping.fd_udp6 > 0) { - close(ping.fd_udp6); - ping.fd_udp6 = -1; - } -} - -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; -} diff --git a/src/fast_ping/fast_ping.c b/src/fast_ping/fast_ping.c new file mode 100755 index 0000000000..7cffd6c9b5 --- /dev/null +++ b/src/fast_ping/fast_ping.c @@ -0,0 +1,686 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_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_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_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; + } + + ret = _fast_ping_get_addr_by_type(type, ip_str, port, &gai, &ping_type); + if (ret != 0) { + goto errout; + } + + ping_host = malloc(sizeof(*ping_host)); + if (ping_host == NULL) { + goto errout; + } + + memset(ping_host, 0, sizeof(*ping_host)); + 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_UDP6: + case FAST_PING_UDP: + ret = _fast_ping_process_udp(ping_host, now); + break; + default: + tlog(TLOG_ERROR, "BUG: type error : %p, %d, %s, %d", ping_host, ping_host->sid, ping_host->host, ping_host->fd); + abort(); + 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(&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(&ping_host->action_list); + _fast_ping_host_put(ping_host); + continue; + } + ping_host->count--; + } + + _fast_ping_sendping(ping_host); + list_del(&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}; + unsigned long last = {0}; + struct timeval tvnow = {0}; + int sleep = 100; + int sleep_time = 0; + unsigned long expect_time = 0; + + setpriority(PRIO_PROCESS, 0, -5); + + sleep_time = sleep; + now = get_tick_count() - sleep; + last = now; + expect_time = now + sleep; + while (atomic_read(&ping.run)) { + now = get_tick_count(); + if (sleep_time > 0) { + sleep_time -= now - last; + if (sleep_time <= 0) { + sleep_time = 0; + } + } + + if (now >= expect_time) { + if (last != now) { + _fast_ping_period_run(); + } + sleep_time = sleep - (now - expect_time); + if (sleep_time < 0) { + sleep_time = 0; + expect_time = now; + } + expect_time += sleep; + } + last = now; + + 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) { + expect_time = get_tick_count(); + } + + if (num == 0) { + continue; + } + + gettimeofday(&tvnow, NULL); + for (i = 0; i < num; i++) { + struct epoll_event *event = &events[i]; + /* read event */ + if (event->data.fd == ping.event_fd) { + 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) +{ + if (ping.fd_icmp > 0) { + close(ping.fd_icmp); + ping.fd_icmp = -1; + } + + if (ping.fd_icmp6 > 0) { + close(ping.fd_icmp6); + ping.fd_icmp6 = -1; + } + + if (ping.fd_udp > 0) { + close(ping.fd_udp); + ping.fd_udp = -1; + } + + if (ping.fd_udp6 > 0) { + close(ping.fd_udp6); + ping.fd_udp6 = -1; + } +} + +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; +} diff --git a/src/fast_ping/fast_ping.h b/src/fast_ping/fast_ping.h new file mode 100755 index 0000000000..e4d13418b6 --- /dev/null +++ b/src/fast_ping/fast_ping.h @@ -0,0 +1,193 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_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 ss_family; + union { + struct sockaddr addr; + struct sockaddr_in6 in6; + struct sockaddr_in in; + }; + socklen_t addr_len; + struct fast_ping_packet packet; + /* for memory address alignment */ + 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 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_ diff --git a/src/fast_ping/notify_event.c b/src/fast_ping/notify_event.c new file mode 100755 index 0000000000..04c69e8a85 --- /dev/null +++ b/src/fast_ping/notify_event.c @@ -0,0 +1,124 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 +#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 = malloc(sizeof(struct fast_ping_notify_event)); + if (notify_event == NULL) { + goto errout; + } + memset(notify_event, 0, sizeof(struct fast_ping_notify_event)); + 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); + } +} \ No newline at end of file diff --git a/src/fast_ping/notify_event.h b/src/fast_ping/notify_event.h new file mode 100755 index 0000000000..32d15d0208 --- /dev/null +++ b/src/fast_ping/notify_event.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_ diff --git a/src/fast_ping/ping_fake.c b/src/fast_ping/ping_fake.c new file mode 100755 index 0000000000..78a8e0277e --- /dev/null +++ b/src/fast_ping/ping_fake.c @@ -0,0 +1,275 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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) { + tlog(TLOG_ERROR, "invalid refcount of fake ping %s", fake->host); + abort(); + } + 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 = malloc(sizeof(*fake)); + if (fake == NULL) { + goto errout; + } + memset(fake, 0, sizeof(*fake)); + + 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); + } +} diff --git a/src/fast_ping/ping_fake.h b/src/fast_ping/ping_fake.h new file mode 100755 index 0000000000..e7b18abd91 --- /dev/null +++ b/src/fast_ping/ping_fake.h @@ -0,0 +1,45 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_ diff --git a/src/fast_ping/ping_host.c b/src/fast_ping/ping_host.c new file mode 100755 index 0000000000..c2b158b7a1 --- /dev/null +++ b/src/fast_ping/ping_host.c @@ -0,0 +1,136 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 +#include +#include +#include + +void _fast_ping_host_get(struct ping_host_struct *ping_host) +{ + if (atomic_inc_return(&ping_host->ref) <= 0) { + tlog(TLOG_ERROR, "BUG: ping host ref is invalid, host: %s", ping_host->host); + abort(); + } +} + +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) { + tlog(TLOG_ERROR, "invalid refcount of ping_host %s", ping_host->host); + abort(); + } + 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); + } +} diff --git a/src/fast_ping/ping_host.h b/src/fast_ping/ping_host.h new file mode 100755 index 0000000000..aac00c9b69 --- /dev/null +++ b/src/fast_ping/ping_host.h @@ -0,0 +1,41 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_ diff --git a/src/fast_ping/ping_icmp.c b/src/fast_ping/ping_icmp.c new file mode 100755 index 0000000000..b2e4b3b860 --- /dev/null +++ b/src/fast_ping/ping_icmp.c @@ -0,0 +1,516 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 == 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; + int buffsize = 64 * 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)); + + 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 = 0; + 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; + } + + if ((uintptr_t)(packet_data + hlen) % __alignof__(void *) == 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) +{ + if (first_addr_len != second_addr_len) { + return -1; + } + + if (first_addr->sa_family != second_addr->sa_family) { + return -1; + } + + switch (first_addr->sa_family) { + case AF_INET: { + struct sockaddr_in *first_addr_in = (struct sockaddr_in *)first_addr; + struct sockaddr_in *second_addr_in = (struct sockaddr_in *)second_addr; + if (memcmp(&first_addr_in->sin_addr.s_addr, &second_addr_in->sin_addr.s_addr, IPV4_ADDR_LEN) != 0) { + return -1; + } + } break; + case AF_INET6: { + struct sockaddr_in6 *first_addr_in6 = (struct sockaddr_in6 *)first_addr; + struct sockaddr_in6 *second_addr_in6 = (struct sockaddr_in6 *)second_addr; + if (memcmp(&first_addr_in6->sin6_addr.s6_addr, &second_addr_in6->sin6_addr.s6_addr, IPV4_ADDR_LEN) != 0) { + return -1; + } + } break; + default: + return -1; + } + + return 0; +} + +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))); +} diff --git a/src/fast_ping/ping_icmp.h b/src/fast_ping/ping_icmp.h new file mode 100755 index 0000000000..537dc494e6 --- /dev/null +++ b/src/fast_ping/ping_icmp.h @@ -0,0 +1,47 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif // !_FAST_PING_ICMP_H_ diff --git a/src/fast_ping/ping_icmp6.c b/src/fast_ping/ping_icmp6.c new file mode 100755 index 0000000000..8dde57811a --- /dev/null +++ b/src/fast_ping/ping_icmp6.c @@ -0,0 +1,176 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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: + 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)); + } + } + + 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; +} diff --git a/src/fast_ping/ping_icmp6.h b/src/fast_ping/ping_icmp6.h new file mode 100755 index 0000000000..4b3e5f5e54 --- /dev/null +++ b/src/fast_ping/ping_icmp6.h @@ -0,0 +1,38 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_ diff --git a/src/fast_ping/ping_tcp.c b/src/fast_ping/ping_tcp.c new file mode 100755 index 0000000000..17d3951610 --- /dev/null +++ b/src/fast_ping/ping_tcp.c @@ -0,0 +1,171 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/fast_ping/ping_tcp.h b/src/fast_ping/ping_tcp.h new file mode 100755 index 0000000000..fde00755d7 --- /dev/null +++ b/src/fast_ping/ping_tcp.h @@ -0,0 +1,39 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_ diff --git a/src/fast_ping/ping_udp.c b/src/fast_ping/ping_udp.c new file mode 100755 index 0000000000..2f190ee282 --- /dev/null +++ b/src/fast_ping/ping_udp.c @@ -0,0 +1,328 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = 0; + + 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 = 0; + 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; +} diff --git a/src/fast_ping/ping_udp.h b/src/fast_ping/ping_udp.h new file mode 100755 index 0000000000..0cc19796c0 --- /dev/null +++ b/src/fast_ping/ping_udp.h @@ -0,0 +1,37 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif // !_FAST_PING_UDP_H_ diff --git a/src/fast_ping/wakeup_event.c b/src/fast_ping/wakeup_event.c new file mode 100755 index 0000000000..02eef44ce5 --- /dev/null +++ b/src/fast_ping/wakeup_event.c @@ -0,0 +1,57 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.fd = fdevent; + 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; +} diff --git a/src/fast_ping/wakeup_event.h b/src/fast_ping/wakeup_event.h new file mode 100755 index 0000000000..b9ec6c732e --- /dev/null +++ b/src/fast_ping/wakeup_event.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_ diff --git a/src/http_parse.c b/src/http_parse.c deleted file mode 100644 index 1585be7bd4..0000000000 --- a/src/http_parse.c +++ /dev/null @@ -1,1534 +0,0 @@ -/************************************************************************* - * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . - * - * smartdns is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * smartdns is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 "http_parse.h" -#include "hash.h" -#include "hashtable.h" -#include "jhash.h" -#include "list.h" -#include "qpack.h" -#include "util.h" -#include -#include -#include -#include - -#define HTTP3_HEADER_FRAME 1 -#define HTTP3_DATA_FRAME 0 - -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); -}; - -/* - * 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 = malloc(sizeof(*http_head)); - if (http_head == NULL) { - goto errout; - } - memset(http_head, 0, sizeof(*http_head)); - 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 = malloc(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; - - 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; - - 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; -} - -static int _http_head_buffer_left_len(struct http_head *http_head) -{ - return http_head->buff_size - http_head->buff_len; -} - -static uint8_t *_http_head_buffer_get_end(struct http_head *http_head) -{ - return http_head->buff + http_head->buff_len; -} - -static 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); -} - -static 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 = malloc(sizeof(*params)); - if (params == NULL) { - return -1; - } - memset(params, 0, sizeof(*params)); - - 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 = malloc(sizeof(*fields)); - if (fields == NULL) { - return -1; - } - memset(fields, 0, sizeof(*fields)); - - 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); -} - -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_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"; - } -} - -static 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; -} - -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 _http_head_parse_http1_1(struct http_head *http_head, const uint8_t *data, int data_len) -{ - int i = 0; - uint8_t *buff_end = NULL; - int left_size = 0; - int process_data_len = 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 < 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 (http_head->head_ok == 0) { - // Read data again */ - http_head->buff_len += process_data_len; - return -1; - } - } - - if (http_head->head_ok == 1) { - 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; - - /* try append null byte */ - if (process_data_len < http_head->buff_size - 1) { - buff_end[0] = '\0'; - } - } - - http_head->buff_len += process_data_len; - if (http_head->data_len < http_head->expect_data_len) { - return -1; - } - - return process_data_len; -} - -static 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; -} - -static int _http_head_parse_http2_0(struct http_head *http_head, const uint8_t *data, int data_len) -{ - return -2; -} - -static int _http_head_serialize_http2_0(struct http_head *http_head, uint8_t *buffer, int buffer_len) -{ - return -2; -} - -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]; - sprintf(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]; - sprintf(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 -1; - } - - 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 -1; - } - offset += offset_ret; - buffer_value[str_len] = '\0'; - if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { - return -1; - } - 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 -1; - } - offset += offset_ret; - buffer_name[str_len] = '\0'; - if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { - return -1; - } - 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 -1; - } - offset += offset_ret; - buffer_value[str_len] = '\0'; - if (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) { - return -1; - } - 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; -} - -static 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; - - 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 ((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 -1; - } - - 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 { - http_head->data = data + offset; - http_head->data_len = frame_len; - } - } else { - return -2; - } - offset += frame_len; - } - - http_head->version = "HTTP/3.0"; - - return offset; -} - -static 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[1024]; - int header_data_len = 0; - - /* serialize header frame. */ - header_data_len = _http3_build_headers_payload(http_head, header_data, sizeof(header_data)); - if (header_data_len < 0) { - return -1; - } - - /* Frame Type: Header*/ - offset_ret = _quicvarint_encode(HTTP3_HEADER_FRAME, buffer + offset, buffer_len - offset); - if (offset_ret < 0) { - return -1; - } - offset += offset_ret; - - /* Header Frmae Length */ - offset_ret = _quicvarint_encode(header_data_len, buffer + offset, buffer_len - offset); - if (offset_ret < 0) { - return -1; - } - offset += offset_ret; - - if (buffer_len - offset < header_data_len) { - return -1; - } - 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) { - return -1; - } - 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) { - return -1; - } - offset += offset_ret; - } - - return offset; -} - -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_2_0) { - return _http_head_parse_http2_0(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_2_0) { - return _http_head_serialize_http2_0(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, *tmp; - struct http_params *params, *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); -} diff --git a/src/http_parse/http1_parse.c b/src/http_parse/http1_parse.c new file mode 100755 index 0000000000..b196bcc62b --- /dev/null +++ b/src/http_parse/http1_parse.c @@ -0,0 +1,477 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; + } + + 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; +} diff --git a/src/http_parse/http1_parse.h b/src/http_parse/http1_parse.h new file mode 100755 index 0000000000..29def5882a --- /dev/null +++ b/src/http_parse/http1_parse.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/http_parse/http2_parse.c b/src/http_parse/http2_parse.c new file mode 100755 index 0000000000..67dd01ea4c --- /dev/null +++ b/src/http_parse/http2_parse.c @@ -0,0 +1,30 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "http2_parse.h" +#include "http_parse.h" + +int http_head_parse_http2_0(struct http_head *http_head, const uint8_t *data, int data_len) +{ + return -2; +} + +int http_head_serialize_http2_0(struct http_head *http_head, uint8_t *buffer, int buffer_len) +{ + return -2; +} \ No newline at end of file diff --git a/src/http_parse/http2_parse.h b/src/http_parse/http2_parse.h new file mode 100755 index 0000000000..b55a09a41e --- /dev/null +++ b/src/http_parse/http2_parse.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_HTTP2_H_ +#define _HTTP_PARSE_HTTP2_H_ + +#include "http_parse.h" + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus */ + +int http_head_parse_http2_0(struct http_head *http_head, const uint8_t *data, int data_len); + +int http_head_serialize_http2_0(struct http_head *http_head, uint8_t *buffer, int buffer_len); + +#ifdef __cplusplus +} +#endif /*__cplusplus */ +#endif diff --git a/src/http_parse/http3_parse.c b/src/http_parse/http3_parse.c new file mode 100755 index 0000000000..ac44f911e1 --- /dev/null +++ b/src/http_parse/http3_parse.c @@ -0,0 +1,674 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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]; + sprintf(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]; + sprintf(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; + } + 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[1024]; + int header_data_len = 0; + + /* serialize header frame. */ + header_data_len = _http3_build_headers_payload(http_head, header_data, sizeof(header_data)); + if (header_data_len < 0) { + return -1; + } + + /* Frame Type: Header*/ + offset_ret = _quicvarint_encode(HTTP3_HEADER_FRAME, buffer + offset, buffer_len - offset); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + + /* Header Frmae Length */ + offset_ret = _quicvarint_encode(header_data_len, buffer + offset, buffer_len - offset); + if (offset_ret < 0) { + return -1; + } + offset += offset_ret; + + if (buffer_len - offset < header_data_len) { + return -1; + } + 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) { + return -1; + } + 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) { + return -1; + } + offset += offset_ret; + } + + return offset; +} \ No newline at end of file diff --git a/src/http_parse/http3_parse.h b/src/http_parse/http3_parse.h new file mode 100755 index 0000000000..b1cbd40034 --- /dev/null +++ b/src/http_parse/http3_parse.h @@ -0,0 +1,35 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/http_parse/http_parse.c b/src/http_parse/http_parse.c new file mode 100755 index 0000000000..bc7d38e756 --- /dev/null +++ b/src/http_parse/http_parse.c @@ -0,0 +1,505 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "http1_parse.h" +#include "http2_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 = malloc(sizeof(*http_head)); + if (http_head == NULL) { + goto errout; + } + memset(http_head, 0, sizeof(*http_head)); + 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 = malloc(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; + + 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; + + 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 = malloc(sizeof(*params)); + if (params == NULL) { + return -1; + } + memset(params, 0, sizeof(*params)); + + 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 = malloc(sizeof(*fields)); + if (fields == NULL) { + return -1; + } + memset(fields, 0, sizeof(*fields)); + + 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_2_0) { + return http_head_parse_http2_0(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_2_0) { + return http_head_serialize_http2_0(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, *tmp; + struct http_params *params, *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); +} diff --git a/src/http_parse/http_parse.h b/src/http_parse/http_parse.h new file mode 100755 index 0000000000..a2a39f2e49 --- /dev/null +++ b/src/http_parse/http_parse.h @@ -0,0 +1,83 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 diff --git a/src/lib/qpack.c b/src/http_parse/qpack.c old mode 100644 new mode 100755 similarity index 100% rename from src/lib/qpack.c rename to src/http_parse/qpack.c diff --git a/src/include/qpack.h b/src/http_parse/qpack.h old mode 100644 new mode 100755 similarity index 100% rename from src/include/qpack.h rename to src/http_parse/qpack.h diff --git a/src/include/smartdns/bloom.h b/src/include/smartdns/bloom.h new file mode 100755 index 0000000000..f4c772baa4 --- /dev/null +++ b/src/include/smartdns/bloom.h @@ -0,0 +1,49 @@ +#ifndef BLOOM_H +#define BLOOM_H + +#include +#include + +// 布隆过滤器结构体 +typedef struct { + uint8_t *bit_array; // 位数组 + size_t size; // 位数组大小 (bits) + size_t num_hashes; // 哈希函数数量 +} bloom_filter_t; + +/** + * @brief 创建并初始化一个新的布隆过滤器。 + * + * @param size 布隆过滤器的大小(位数)。 + * @param num_hashes 哈希函数的数量。 + * @return 指向新创建的布隆过滤器的指针,如果失败则返回 NULL。 + */ +bloom_filter_t *bloom_filter_new(size_t size, size_t num_hashes); + +/** + * @brief 释放布隆过滤器占用的内存。 + * + * @param bf 指向要释放的布隆过滤器的指针。 + */ +void bloom_filter_free(bloom_filter_t *bf); + +/** + * @brief 向布隆过滤器中添加一个元素。 + * + * @param bf 指向布隆过滤器的指针。 + * @param item 指向要添加的元素的指针。 + * @param item_len 要添加的元素的长度(字节)。 + */ +void bloom_filter_add(bloom_filter_t *bf, const void *item, size_t item_len); + +/** + * @brief 检查一个元素是否可能在布隆过滤器中。 + * + * @param bf 指向布隆过滤器的指针。 + * @param item 指向要检查的元素的指针。 + * @param item_len 要检查的元素的长度(字节)。 + * @return 如果元素可能在过滤器中,则返回 1;否则返回 0。 + */ +int bloom_filter_check(bloom_filter_t *bf, const void *item, size_t item_len); + +#endif // BLOOM_H \ No newline at end of file diff --git a/src/dns.h b/src/include/smartdns/dns.h old mode 100644 new mode 100755 similarity index 99% rename from src/dns.h rename to src/include/smartdns/dns.h index 30bd240a11..d497439d47 --- a/src/dns.h +++ b/src/include/smartdns/dns.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/dns_cache.h b/src/include/smartdns/dns_cache.h old mode 100644 new mode 100755 similarity index 94% rename from src/dns_cache.h rename to src/include/smartdns/dns_cache.h index f1d6653072..9cfe4809be --- a/src/dns_cache.h +++ b/src/include/smartdns/dns_cache.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,13 +19,13 @@ #ifndef _SMARTDNS_CACHE_H #define _SMARTDNS_CACHE_H -#include "atomic.h" -#include "dns.h" -#include "dns_conf.h" -#include "hash.h" -#include "hashtable.h" -#include "list.h" -#include "timer.h" +#include "smartdns/dns.h" +#include "smartdns/dns_conf.h" +#include "smartdns/lib/atomic.h" +#include "smartdns/lib/hash.h" +#include "smartdns/lib/hashtable.h" +#include "smartdns/lib/list.h" +#include "smartdns/timer.h" #include #include diff --git a/src/dns_client.h b/src/include/smartdns/dns_client.h old mode 100644 new mode 100755 similarity index 98% rename from src/dns_client.h rename to src/include/smartdns/dns_client.h index c3b5259be6..0e8c4be889 --- a/src/dns_client.h +++ b/src/include/smartdns/dns_client.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ #ifndef _SMART_DNS_CLIENT_H #define _SMART_DNS_CLIENT_H -#include "dns.h" +#include "smartdns/dns.h" #ifdef __cplusplus extern "C" { diff --git a/src/dns_conf.h b/src/include/smartdns/dns_conf.h old mode 100644 new mode 100755 similarity index 97% rename from src/dns_conf.h rename to src/include/smartdns/dns_conf.h index 388732a9a0..2e320a4751 --- a/src/dns_conf.h +++ b/src/include/smartdns/dns_conf.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,16 +19,16 @@ #ifndef _DNS_CONF #define _DNS_CONF -#include "art.h" -#include "atomic.h" -#include "conf.h" -#include "dns.h" -#include "dns_client.h" -#include "hash.h" -#include "hashtable.h" -#include "list.h" -#include "proxy.h" -#include "radix.h" +#include "smartdns/dns.h" +#include "smartdns/dns_client.h" +#include "smartdns/lib/art.h" +#include "smartdns/lib/atomic.h" +#include "smartdns/lib/conf.h" +#include "smartdns/lib/hash.h" +#include "smartdns/lib/hashtable.h" +#include "smartdns/lib/list.h" +#include "smartdns/lib/radix.h" +#include "smartdns/proxy.h" #ifdef __cplusplus extern "C" { @@ -566,6 +566,7 @@ struct dns_domain_set_rule { enum dns_domain_set_type { DNS_DOMAIN_SET_LIST = 0, DNS_DOMAIN_SET_GEOSITE = 1, + DNS_DOMAIN_SET_GEOSITELIST = 2, }; struct dns_domain_set_name { @@ -682,7 +683,9 @@ struct dns_config { char bind_ca_file[DNS_MAX_PATH]; char bind_ca_key_file[DNS_MAX_PATH]; + char bind_root_ca_key_file[DNS_MAX_PATH]; char bind_ca_key_pass[DNS_MAX_PATH]; + int bind_ca_validity_days; char need_cert; int tcp_idle_time; ssize_t cachesize; @@ -784,6 +787,8 @@ const char *dns_conf_get_cache_dir(void); const char *dns_conf_get_data_dir(void); +const char *dns_conf_get_ddns_domain(void); + #ifdef __cplusplus } #endif diff --git a/src/dns_plugin.h b/src/include/smartdns/dns_plugin.h old mode 100644 new mode 100755 similarity index 99% rename from src/dns_plugin.h rename to src/include/smartdns/dns_plugin.h index 71559947de..6d16f9f537 --- a/src/dns_plugin.h +++ b/src/include/smartdns/dns_plugin.h @@ -19,7 +19,7 @@ #ifndef SMART_DNS_PLUGIN_H #define SMART_DNS_PLUGIN_H -#include "dns.h" +#include "smartdns/dns.h" #include #ifdef __cplusplus diff --git a/src/dns_server.h b/src/include/smartdns/dns_server.h old mode 100644 new mode 100755 similarity index 96% rename from src/dns_server.h rename to src/include/smartdns/dns_server.h index 6e94eee2b3..6d5b4fd07e --- a/src/dns_server.h +++ b/src/include/smartdns/dns_server.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ #ifndef _SMART_DNS_SERVER_H #define _SMART_DNS_SERVER_H -#include "dns.h" -#include "dns_client.h" +#include "smartdns/dns.h" +#include "smartdns/dns_client.h" #include #ifdef __cplusplus diff --git a/src/dns_stats.h b/src/include/smartdns/dns_stats.h old mode 100644 new mode 100755 similarity index 87% rename from src/dns_stats.h rename to src/include/smartdns/dns_stats.h index c82776a88a..f8dee926f0 --- a/src/dns_stats.h +++ b/src/include/smartdns/dns_stats.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ #ifndef SMART_DNS_STATS_H #define SMART_DNS_STATS_H -#include "atomic.h" +#include "smartdns/lib/atomic.h" #include #ifdef __cplusplus @@ -65,7 +65,11 @@ static inline uint64_t stats_read(const uint64_t *s) static inline uint64_t stats_read_and_set(uint64_t *s, uint64_t v) { +#ifdef USE_ATOMIC + return __atomic_test_and_set(s, v); +#else return __sync_lock_test_and_set(s, v); +#endif } static inline void stats_set(uint64_t *s, uint64_t v) @@ -75,22 +79,38 @@ static inline void stats_set(uint64_t *s, uint64_t v) static inline void stats_add(uint64_t *s, uint64_t v) { +#ifdef USE_ATOMIC + (void)__atomic_add_fetch(s, v, __ATOMIC_SEQ_CST); +#else (void)__sync_add_and_fetch(s, v); +#endif } static inline void stats_inc(uint64_t *s) { +#ifdef USE_ATOMIC + (void)__atomic_add_fetch(s, 1, __ATOMIC_SEQ_CST); +#else (void)__sync_add_and_fetch(s, 1); +#endif } static inline void stats_sub(uint64_t *s, uint64_t v) { +#ifdef USE_ATOMIC + (void)__atomic_sub_fetch(s, v, __ATOMIC_SEQ_CST); +#else (void)__sync_sub_and_fetch(s, v); +#endif } static inline void stats_dec(uint64_t *s) { +#ifdef USE_ATOMIC + (void)__atomic_sub_fetch(s, 1, __ATOMIC_SEQ_CST); +#else (void)__sync_sub_and_fetch(s, 1); +#endif } void dns_stats_avg_time_update(struct dns_stats_avg_time *avg_time); diff --git a/src/fast_ping.h b/src/include/smartdns/fast_ping.h old mode 100644 new mode 100755 similarity index 96% rename from src/fast_ping.h rename to src/include/smartdns/fast_ping.h index b916e817f8..9fa059f254 --- a/src/fast_ping.h +++ b/src/include/smartdns/fast_ping.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/http_parse.h b/src/include/smartdns/http_parse.h old mode 100644 new mode 100755 similarity index 98% rename from src/http_parse.h rename to src/include/smartdns/http_parse.h index 0700d4862b..9a334d7c42 --- a/src/http_parse.h +++ b/src/include/smartdns/http_parse.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/art.h b/src/include/smartdns/lib/art.h old mode 100644 new mode 100755 similarity index 100% rename from src/include/art.h rename to src/include/smartdns/lib/art.h diff --git a/src/include/atomic.h b/src/include/smartdns/lib/atomic.h old mode 100644 new mode 100755 similarity index 98% rename from src/include/atomic.h rename to src/include/smartdns/lib/atomic.h index 13a03d4624..a518bfd03f --- a/src/include/atomic.h +++ b/src/include/smartdns/lib/atomic.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/bitmap.h b/src/include/smartdns/lib/bitmap.h old mode 100644 new mode 100755 similarity index 98% rename from src/include/bitmap.h rename to src/include/smartdns/lib/bitmap.h index 1ece376c58..e234229725 --- a/src/include/bitmap.h +++ b/src/include/smartdns/lib/bitmap.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/bitops.h b/src/include/smartdns/lib/bitops.h old mode 100644 new mode 100755 similarity index 98% rename from src/include/bitops.h rename to src/include/smartdns/lib/bitops.h index c39635397f..199528c65d --- a/src/include/bitops.h +++ b/src/include/smartdns/lib/bitops.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/conf.h b/src/include/smartdns/lib/conf.h old mode 100644 new mode 100755 similarity index 98% rename from src/include/conf.h rename to src/include/smartdns/lib/conf.h index 85d3359722..392f67b04d --- a/src/include/conf.h +++ b/src/include/smartdns/lib/conf.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -208,7 +208,7 @@ typedef int(conf_error_handler)(const char *key, const char *value, const char * int conf_parse_key_values(char *line, int *key_num, char **keys, char **values); -int load_conf(const char *file, struct config_item items[], conf_error_handler handler); +int load_conf(const char *file, const struct config_item items[], conf_error_handler handler); void load_exit(void); diff --git a/src/include/findbit.h b/src/include/smartdns/lib/findbit.h old mode 100644 new mode 100755 similarity index 97% rename from src/include/findbit.h rename to src/include/smartdns/lib/findbit.h index 0a451573c6..704131c5d7 --- a/src/include/findbit.h +++ b/src/include/smartdns/lib/findbit.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/gcc_builtin.h b/src/include/smartdns/lib/gcc_builtin.h old mode 100644 new mode 100755 similarity index 98% rename from src/include/gcc_builtin.h rename to src/include/smartdns/lib/gcc_builtin.h index c043744c4b..8d8e45a998 --- a/src/include/gcc_builtin.h +++ b/src/include/smartdns/lib/gcc_builtin.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/hash.h b/src/include/smartdns/lib/hash.h old mode 100644 new mode 100755 similarity index 99% rename from src/include/hash.h rename to src/include/smartdns/lib/hash.h index 2270ed2585..7434d99d5a --- a/src/include/hash.h +++ b/src/include/smartdns/lib/hash.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/hashtable.h b/src/include/smartdns/lib/hashtable.h old mode 100644 new mode 100755 similarity index 99% rename from src/include/hashtable.h rename to src/include/smartdns/lib/hashtable.h index 5231fba27d..a4d58ff1f5 --- a/src/include/hashtable.h +++ b/src/include/smartdns/lib/hashtable.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/idna.h b/src/include/smartdns/lib/idna.h old mode 100644 new mode 100755 similarity index 93% rename from src/include/idna.h rename to src/include/smartdns/lib/idna.h index ca8efae297..c68b613996 --- a/src/include/idna.h +++ b/src/include/smartdns/lib/idna.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/jhash.h b/src/include/smartdns/lib/jhash.h old mode 100644 new mode 100755 similarity index 98% rename from src/include/jhash.h rename to src/include/smartdns/lib/jhash.h index 4649d89af4..90de71bbe3 --- a/src/include/jhash.h +++ b/src/include/smartdns/lib/jhash.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/list.h b/src/include/smartdns/lib/list.h old mode 100644 new mode 100755 similarity index 99% rename from src/include/list.h rename to src/include/smartdns/lib/list.h index f4ff038fa8..30a014a945 --- a/src/include/list.h +++ b/src/include/smartdns/lib/list.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/nftset.h b/src/include/smartdns/lib/nftset.h old mode 100644 new mode 100755 similarity index 100% rename from src/include/nftset.h rename to src/include/smartdns/lib/nftset.h diff --git a/src/include/radix.h b/src/include/smartdns/lib/radix.h old mode 100644 new mode 100755 similarity index 100% rename from src/include/radix.h rename to src/include/smartdns/lib/radix.h diff --git a/src/include/rbtree.h b/src/include/smartdns/lib/rbtree.h old mode 100644 new mode 100755 similarity index 99% rename from src/include/rbtree.h rename to src/include/smartdns/lib/rbtree.h index 53fe7a878e..ed0bcbc92c --- a/src/include/rbtree.h +++ b/src/include/smartdns/lib/rbtree.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/stringutil.h b/src/include/smartdns/lib/stringutil.h old mode 100644 new mode 100755 similarity index 95% rename from src/include/stringutil.h rename to src/include/smartdns/lib/stringutil.h index 97dbb6f917..a2317b1413 --- a/src/include/stringutil.h +++ b/src/include/smartdns/lib/stringutil.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/timer_wheel.h b/src/include/smartdns/lib/timer_wheel.h old mode 100644 new mode 100755 similarity index 96% rename from src/include/timer_wheel.h rename to src/include/smartdns/lib/timer_wheel.h index 679fc0b1fc..853e38f4ee --- a/src/include/timer_wheel.h +++ b/src/include/smartdns/lib/timer_wheel.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/proxy.h b/src/include/smartdns/proxy.h old mode 100644 new mode 100755 similarity index 97% rename from src/proxy.h rename to src/include/smartdns/proxy.h index 88b5c68470..3b69459bab --- a/src/proxy.h +++ b/src/include/smartdns/proxy.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/include/smartdns/regexp.h b/src/include/smartdns/regexp.h new file mode 100755 index 0000000000..445f4f6456 --- /dev/null +++ b/src/include/smartdns/regexp.h @@ -0,0 +1,80 @@ +/************************************************************************* + * + * Copyright (C) 2018-2023 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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_REGEXP_H +#define _SMARTDNS_REGEXP_H +#include "smartdns/dns.h" +#include "smartdns/lib/list.h" +#include +#include +//#include +#include +#include "smartdns/bloom.h" + +#ifdef __cpluscplus +extern "C" { +#endif + +#define DNS_MAX_REGEXP_LEN 256 +#define DNS_MAX_REGEXP_NUM 200 + +struct dns_regexp { + struct list_head list; + char regexp[DNS_MAX_REGEXP_LEN]; + //regex_t regex; + cre2_regexp_t *rex; + cre2_options_t *opt; +}; + +extern bloom_filter_t *g_regexp_bloom_filter; + +// 初始化正则表达式布隆过滤器 +int dns_regexp_bloom_filter_init(size_t size, size_t num_hashes); + +// 释放正则表达式布隆过滤器 +void dns_regexp_bloom_filter_free(void); + +// 将正则表达式模式添加到布隆过滤器 +void dns_regexp_bloom_filter_add_pattern(const char *pattern); + +// 检查域名是否可能匹配布隆过滤器中的任何模式 +// 返回1表示可能匹配,0表示绝对不匹配 +int dns_regexp_bloom_filter_check_domain(const char *domain); + +int dns_regexp_init(void); + +__attribute__((unused)) struct dns_regexp *_dns_regexp_last(void); + +void _dns_regexp_delete(struct dns_regexp *dns_regexp); + +void dns_regexp_release(struct dns_regexp *dns_regexp); + +int has_regexp(void); + +int _dns_regexp_insert(char *regexp, struct list_head *head); + +int dns_regexp_insert(char *regexp); + +int dns_regexp_match(const char *string, char *matched_pattern); + +void dns_regexp_destroy(void); + +#ifdef __cpluscplus +} +#endif +#endif // !_SMARTDNS_REGEXP_H \ No newline at end of file diff --git a/src/smartdns.h b/src/include/smartdns/smartdns.h old mode 100644 new mode 100755 similarity index 81% rename from src/smartdns.h rename to src/include/smartdns/smartdns.h index 8a43b950c2..5092f646f1 --- a/src/smartdns.h +++ b/src/include/smartdns/smartdns.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,15 +19,14 @@ #ifndef SMART_DNS_H #define SMART_DNS_H - -#include "dns_cache.h" -#include "dns_client.h" -#include "dns_conf.h" -#include "dns_plugin.h" -#include "dns_server.h" -#include "fast_ping.h" -#include "dns_stats.h" -#include "util.h" +#include "smartdns/dns_cache.h" +#include "smartdns/dns_client.h" +#include "smartdns/dns_conf.h" +#include "smartdns/dns_plugin.h" +#include "smartdns/dns_server.h" +#include "smartdns/dns_stats.h" +#include "smartdns/fast_ping.h" +#include "smartdns/util.h" #ifdef __cplusplus extern "C" { diff --git a/src/timer.h b/src/include/smartdns/timer.h old mode 100644 new mode 100755 similarity index 91% rename from src/timer.h rename to src/include/smartdns/timer.h index c5310bceff..2fdeaa0cb8 --- a/src/timer.h +++ b/src/include/smartdns/timer.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ #ifndef SMART_DNS_TIMER_H #define SMART_DNS_TIMER_H -#include "timer_wheel.h" +#include "smartdns/lib/timer_wheel.h" #ifdef __cplusplus extern "C" { diff --git a/src/tlog.h b/src/include/smartdns/tlog.h old mode 100644 new mode 100755 similarity index 99% rename from src/tlog.h rename to src/include/smartdns/tlog.h index 25079c81ec..8236d8b42c --- a/src/tlog.h +++ b/src/include/smartdns/tlog.h @@ -1,6 +1,6 @@ /* * tinylog - * Copyright (C) 2018-2024 Ruilin Peng (Nick) + * Copyright (C) 2018-2025 Ruilin Peng (Nick) * https://github.com/pymumu/tinylog */ diff --git a/src/util.h b/src/include/smartdns/util.h old mode 100644 new mode 100755 similarity index 93% rename from src/util.h rename to src/include/smartdns/util.h index 3d71d0268e..9ee7432f1b --- a/src/util.h +++ b/src/include/smartdns/util.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ #ifndef SMART_DNS_UTIL_H #define SMART_DNS_UTIL_H -#include "stringutil.h" +#include "smartdns/lib/stringutil.h" #include #include @@ -45,6 +45,11 @@ extern "C" { #define PORT_NOT_DEFINED -1 #define MAX_IP_LEN 64 +#define IPV6_ADDR_LEN 16 +#define IPV4_ADDR_LEN 4 + +#define TMP_BUFF_LEN_32 32 + #ifndef BASE_FILE_NAME #define BASE_FILE_NAME (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__) #endif @@ -131,7 +136,11 @@ int SSL_base64_decode_ext(const char *in, unsigned char *out, int max_outlen, in int SSL_base64_encode(const void *in, int in_len, char *out); -int generate_cert_key(const char *key_path, const char *cert_path, const char *san, int days); +int generate_cert_key(const char *key_path, const char *cert_path, const char *root_key_path, const char *san, int days); + +int generate_cert_san(char *san, int max_san_len, const char *append_san); + +int is_cert_valid(const char *cert_file_path); int create_pid_file(const char *pid_file); @@ -197,6 +206,8 @@ int dns_packet_debug(const char *packet_file); int dns_is_quic_supported(void); +int decode_hex(int ch); + #ifdef __cplusplus } #endif /*__cplusplus */ diff --git a/src/lib/art.c b/src/lib/art.c old mode 100644 new mode 100755 index 327a8a526d..747fc7c682 --- a/src/lib/art.c +++ b/src/lib/art.c @@ -29,7 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include "art.h" +#include "smartdns/lib/art.h" // #ifdef __i386__ // #include diff --git a/src/lib/bitops.c b/src/lib/bitops.c old mode 100644 new mode 100755 index bb541096c1..7628dabd9b --- a/src/lib/bitops.c +++ b/src/lib/bitops.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -#include "bitmap.h" -#include "bitops.h" +#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 diff --git a/src/lib/conf.c b/src/lib/conf.c old mode 100644 new mode 100755 index 082ae7eec2..66019324c6 --- a/src/lib/conf.c +++ b/src/lib/conf.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include "conf.h" +#include "smartdns/lib/conf.h" #include #include #include @@ -207,7 +207,7 @@ int conf_size(const char *item, void *data, int argc, char *argv[]) return -1; } - size = num * base; + size = (size_t)num * base; if (size > item_size->max) { size = item_size->max; } else if (size < item_size->min) { @@ -240,7 +240,7 @@ int conf_ssize(const char *item, void *data, int argc, char *argv[]) } num = atoi(value); - size = num * base; + size = (ssize_t)num * base; if (size > item_size->max) { size = item_size->max; } else if (size < item_size->min) { @@ -469,7 +469,7 @@ static int load_conf_printf(const char *key, const char *value, const char *file return 0; } -static int load_conf_file(const char *file, struct config_item *items, conf_error_handler handler) +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]; @@ -619,7 +619,7 @@ static int load_conf_file(const char *file, struct config_item *items, conf_erro return -1; } -int load_conf(const char *file, struct config_item items[], conf_error_handler handler) +int load_conf(const char *file, const struct config_item items[], conf_error_handler handler) { return load_conf_file(file, items, handler); } diff --git a/src/lib/idna.c b/src/lib/idna.c old mode 100644 new mode 100755 index 228a91504f..4fe78d137a --- a/src/lib/idna.c +++ b/src/lib/idna.c @@ -1,7 +1,7 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +18,7 @@ */ #define _GNU_SOURCE -#include "idna.h" +#include "smartdns/lib/idna.h" #include static unsigned _utf8_decode_slow(const char **p, const char *pe, unsigned a) diff --git a/src/lib/nftset.c b/src/lib/nftset.c old mode 100644 new mode 100755 index ac99645778..aa09de78d6 --- a/src/lib/nftset.c +++ b/src/lib/nftset.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,9 +17,9 @@ */ #define _GNU_SOURCE /* See feature_test_macros(7) */ -#include "nftset.h" -#include "../dns_conf.h" -#include "../tlog.h" +#include "smartdns/lib/nftset.h" +#include "smartdns/dns_conf.h" +#include "smartdns/tlog.h" #include #include #include diff --git a/src/lib/radix.c b/src/lib/radix.c old mode 100644 new mode 100755 index c077383d62..da54ad04af --- a/src/lib/radix.c +++ b/src/lib/radix.c @@ -55,12 +55,14 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define _GNU_SOURCE + #include #include #include #include -#include "radix.h" +#include "smartdns/lib/radix.h" /* $Id: radix.c,v 1.17 2007/10/24 06:04:31 djm Exp $ */ diff --git a/src/lib/rbtree.c b/src/lib/rbtree.c old mode 100644 new mode 100755 index d6f15b97b5..5317a9fbd7 --- a/src/lib/rbtree.c +++ b/src/lib/rbtree.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include "rbtree.h" +#include "smartdns/lib/rbtree.h" #include static inline void rb_set_black(struct rb_node *rb) diff --git a/src/lib/stringutil.c b/src/lib/stringutil.c old mode 100644 new mode 100755 index 96846940bc..ebbdf1e554 --- a/src/lib/stringutil.c +++ b/src/lib/stringutil.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/timer_wheel.c b/src/lib/timer_wheel.c old mode 100644 new mode 100755 index 1ebf0b7d9e..aaf5c6ccca --- a/src/lib/timer_wheel.c +++ b/src/lib/timer_wheel.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -#include "bitops.h" + #define _GNU_SOURCE + +#include "smartdns/lib/bitops.h" #include #include #include @@ -25,7 +27,7 @@ #include #include -#include "timer_wheel.h" +#include "smartdns/lib/timer_wheel.h" #define TVR_BITS 10 #define TVN_BITS 6 diff --git a/src/main.c b/src/main.c old mode 100644 new mode 100755 index b4fd84ea54..c728218e4f --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include "smartdns.h" +#include "smartdns/smartdns.h" #include #include #include diff --git a/src/proxy.c b/src/proxy.c old mode 100644 new mode 100755 index 2c30c59299..dc2393f4ea --- a/src/proxy.c +++ b/src/proxy.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,13 +19,13 @@ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include "proxy.h" -#include "dns_conf.h" -#include "hashtable.h" -#include "http_parse.h" -#include "list.h" -#include "tlog.h" -#include "util.h" +#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 diff --git a/src/regexp.c b/src/regexp.c new file mode 100755 index 0000000000..e1c126e656 --- /dev/null +++ b/src/regexp.c @@ -0,0 +1,249 @@ +/************************************************************************* + * + * Copyright (C) 2018-2023 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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/regexp.h" +#include "smartdns/lib/stringutil.h" +#include "smartdns/tlog.h" +#include +#include +#include +#include +#include + +// 从 regexp.h 移到此处,因为这是实现文件 +// #include "../utils/bloom.h" // 应通过 regexp.h 包含 + +/* regexp list */ +struct dns_regexp_head { + struct list_head regexp_list; + int num; +}; + +static struct dns_regexp_head dns_regexp_head; + +// 全局布隆过滤器实例 +bloom_filter_t *g_regexp_bloom_filter = NULL; + +// 默认布隆过滤器参数 (可根据需要调整或设为可配置) +#define DEFAULT_BLOOM_FILTER_SIZE (1024 * 8) // 1KB 位数组 +#define DEFAULT_BLOOM_FILTER_HASHES 7 + +int dns_regexp_bloom_filter_init(size_t size, size_t num_hashes) { + if (g_regexp_bloom_filter != NULL) { + // 已经初始化,可能需要先释放或返回错误 + tlog(TLOG_WARN, "Bloom filter already initialized."); + return 0; // 或者返回错误码 + } + size_t bf_size = (size > 0) ? size : DEFAULT_BLOOM_FILTER_SIZE; + size_t bf_hashes = (num_hashes > 0) ? num_hashes : DEFAULT_BLOOM_FILTER_HASHES; + + g_regexp_bloom_filter = bloom_filter_new(bf_size, bf_hashes); + if (g_regexp_bloom_filter == NULL) { + tlog(TLOG_ERROR, "Failed to initialize regexp bloom filter."); + return -1; + } + tlog(TLOG_INFO, "Regexp bloom filter initialized (size: %zu bits, hashes: %zu).", bf_size, bf_hashes); + return 0; +} + +void dns_regexp_bloom_filter_free(void) { + if (g_regexp_bloom_filter != NULL) { + bloom_filter_free(g_regexp_bloom_filter); + g_regexp_bloom_filter = NULL; + tlog(TLOG_INFO, "Regexp bloom filter freed."); + } +} + +void dns_regexp_bloom_filter_add_pattern(const char *pattern) { + if (g_regexp_bloom_filter != NULL && pattern != NULL) { + bloom_filter_add(g_regexp_bloom_filter, pattern, strlen(pattern)); + // tlog(TLOG_DEBUG, "Added pattern to bloom filter: %s", pattern); + } +} + +int dns_regexp_bloom_filter_check_domain(const char *domain) { + if (g_regexp_bloom_filter != NULL && domain != NULL) { + return bloom_filter_check(g_regexp_bloom_filter, domain, strlen(domain)); + } + return 1; // 如果过滤器未初始化或输入无效,保守地返回可能匹配 +} + +int dns_regexp_init(void) +{ + INIT_LIST_HEAD(&dns_regexp_head.regexp_list); + dns_regexp_head.num=0; + // 初始化布隆过滤器 + if (dns_regexp_bloom_filter_init(0, 0) != 0) { // 使用默认大小和哈希数 + // 处理初始化失败的情况,可能记录错误日志或阻止启动 + tlog(TLOG_ERROR, "Failed to initialize bloom filter during regexp_init."); + // 根据项目错误处理策略,这里可能需要返回错误 + } + return 0; +} + +__attribute__((unused)) struct dns_regexp *_dns_regexp_last(void) +{ + return list_last_entry(&dns_regexp_head.regexp_list, struct dns_regexp, list); +} + +void _dns_regexp_delete(struct dns_regexp *dns_regexp) +{ + list_del_init(&dns_regexp->list); + free(dns_regexp); +} + +void dns_regexp_release(struct dns_regexp *dns_regexp) +{ + if (dns_regexp == NULL) { + return; + } + + _dns_regexp_delete(dns_regexp); +} + +int has_regexp(void) +{ + return (dns_regexp_head.num>0?1:0); +} + +int _dns_regexp_insert(char *regexp, struct list_head *head) +{ + struct dns_regexp *dns_regexp = NULL; + + dns_regexp = malloc(sizeof(*dns_regexp)); + if (dns_regexp == NULL) { + goto errout; + } + + memset(dns_regexp, 0, sizeof(*dns_regexp)); + /* + int reti=regcomp(&dns_regexp->regex,regexp,REG_EXTENDED |REG_NOSUB); + if ( reti != REG_NOERROR) { + char msgbuf[100]; + regerror(reti, &dns_regexp->regex, msgbuf, sizeof(msgbuf)); + tlog(TLOG_ERROR, "compile regexp: %s, error %s", regexp, msgbuf); + regfree(&dns_regexp->regex); + goto errout; + } + */ + + dns_regexp->opt = cre2_opt_new(); + cre2_opt_set_perl_classes(dns_regexp->opt, 1); + dns_regexp->rex = cre2_new(regexp, strlen(regexp), dns_regexp->opt); + int err = cre2_error_code(dns_regexp->rex); + if (err) { + tlog(TLOG_ERROR, "compile regexp: %s error=%s", regexp, cre2_error_string(dns_regexp->rex)); + cre2_delete(dns_regexp->rex); + cre2_opt_delete(dns_regexp->opt); + goto errout; + } + + //tlog(TLOG_INFO, "compile regexp: %s", regexp); + safe_strncpy(dns_regexp->regexp, regexp, DNS_MAX_REGEXP_LEN); + list_add_tail(&dns_regexp->list, head); + + // 将模式添加到布隆过滤器 + dns_regexp_bloom_filter_add_pattern(regexp); + + return 0; +errout: + if (dns_regexp) { + free(dns_regexp); + } + + return -1; +} + +int dns_regexp_insert(char *regexp) +{ + if (regexp == NULL) { + return -1; + } + + if (dns_regexp_head.num>=DNS_MAX_REGEXP_NUM) { + return -1; + } + + if (_dns_regexp_insert(regexp, &dns_regexp_head.regexp_list)==0) { + dns_regexp_head.num++; + return 0; + } + + return -1; +} + +int dns_regexp_match(const char *domain, char *regexp) +{ + struct dns_regexp *dns_regexp_entry = NULL; + struct dns_regexp *tmp = NULL; + + if (domain == NULL || regexp == NULL) { // Check domain as well + return -1; // Invalid arguments + } + + // 使用布隆过滤器进行初筛 + if (g_regexp_bloom_filter != NULL && dns_regexp_head.num > 0) { // 仅当有正则且过滤器已初始化时检查 + if (!dns_regexp_bloom_filter_check_domain(domain)) { + // tlog(TLOG_DEBUG, "Bloom filter: domain %s likely NOT in regexp set.", domain); + return -1; // 布隆过滤器指示不匹配,可以提前返回 + } + // tlog(TLOG_DEBUG, "Bloom filter: domain %s MAY BE in regexp set.", domain); + } + + list_for_each_entry_safe(dns_regexp_entry, tmp, &dns_regexp_head.regexp_list, list) { + cre2_string_t match_details; // Renamed from 'match' to avoid conflict if any + int nmatch = 1; + int e = cre2_match(dns_regexp_entry->rex, domain, strlen(domain), 0, strlen(domain), CRE2_UNANCHORED, &match_details, nmatch); + switch (e) { + case 1: // Match + safe_strncpy(regexp, dns_regexp_entry->regexp, DNS_MAX_REGEXP_LEN); + tlog(TLOG_INFO, "domain %s match regexp: %s", domain, dns_regexp_entry->regexp); + return 0; // Success (matched) + + case 0: // No match + break; + + default: // Error + if (cre2_error_code(dns_regexp_entry->rex)) { + tlog(TLOG_ERROR, "domain %s match regexp: %s, error %s", domain, dns_regexp_entry->regexp, + cre2_error_string(dns_regexp_entry->rex)); + } + } + } + + return -1; // No match found after iterating all regexps +} + +void dns_regexp_destroy(void) +{ + struct dns_regexp *dns_regexp_entry = NULL; + struct dns_regexp *tmp = NULL; + + // 释放布隆过滤器 + dns_regexp_bloom_filter_free(); + + list_for_each_entry_safe(dns_regexp_entry, tmp, &dns_regexp_head.regexp_list, list) + { + //regfree(&dns_regexp->regex); + cre2_delete(dns_regexp_entry->rex); + cre2_opt_delete(dns_regexp_entry->opt); + _dns_regexp_delete(dns_regexp_entry); // _dns_regexp_delete already calls list_del_init and free + } + dns_regexp_head.num = 0; // Reset count after clearing +} diff --git a/src/smartdns.c b/src/smartdns.c old mode 100644 new mode 100755 index 36361e0c3b..3347a17c14 --- a/src/smartdns.c +++ b/src/smartdns.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,15 +18,15 @@ #define _GNU_SOURCE -#include "smartdns.h" +#include "smartdns/smartdns.h" -#include "art.h" -#include "atomic.h" -#include "hashtable.h" -#include "list.h" -#include "rbtree.h" -#include "timer.h" -#include "tlog.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 @@ -379,6 +379,10 @@ 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; @@ -390,11 +394,38 @@ static int _smartdns_create_cert(void) 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) { - return 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, NULL, 365 * 3) != 0) { + 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; } @@ -786,7 +817,6 @@ static int _smartdns_create_datadir(void) int unused __attribute__((unused)) = 0; safe_strncpy(data_dir, dns_conf_get_data_dir(), PATH_MAX); - dir_name(data_dir); if (get_uid_gid(&uid, &gid) != 0) { return -1; @@ -983,9 +1013,10 @@ int smartdns_reg_post_func(smartdns_post_func func, void *arg) #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) { - write(fd_notify, &retval, sizeof(retval)); + unused = write(fd_notify, &retval, sizeof(retval)); } if (_smartdns_post != NULL) { diff --git a/src/timer.c b/src/timer.c old mode 100644 new mode 100755 index b70eaee5a0..80cedae257 --- a/src/timer.c +++ b/src/timer.c @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -#include "timer.h" -#include "timer_wheel.h" +#include "smartdns/timer.h" +#include "smartdns/lib/timer_wheel.h" static struct tw_base *dns_timer_base = NULL; @@ -67,4 +67,3 @@ int dns_timer_mod(struct tw_timer_list *timer, unsigned long expires) return tw_mod_timer(dns_timer_base, timer, expires); } - diff --git a/src/tlog.c b/src/tlog.c old mode 100644 new mode 100755 index e4c4031dad..ad5c639314 --- a/src/tlog.c +++ b/src/tlog.c @@ -1,12 +1,12 @@ /* * tinylog - * Copyright (C) 2018-2024 Nick Peng + * Copyright (C) 2018-2025 Nick Peng * https://github.com/pymumu/tinylog */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include "tlog.h" +#include "smartdns/tlog.h" #include #include #include @@ -761,7 +761,7 @@ int tlog_vext(tlog_level level, const char *file, int line, const char *func, vo return _tlog_early_print(&info_inter, format, ap); } - if (unlikely(tlog.root->logsize <= 0)) { + if (unlikely(tlog.root->logsize <= 0 && tlog.root->logscreen == 0 && tlog.root->set_custom_output_func == 0)) { return 0; } @@ -1269,7 +1269,7 @@ static int _tlog_write(struct tlog_log *log, const char *buff, int bufflen) _tlog_archive_log(log); } - if (log->fd <= 0) { + if (log->fd <= 0 && log->logsize > 0) { /* open a new log file to write */ time_t now; diff --git a/src/util.c b/src/util.c deleted file mode 100644 index 39d89f052e..0000000000 --- a/src/util.c +++ /dev/null @@ -1,2679 +0,0 @@ -/************************************************************************* - * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . - * - * smartdns is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * smartdns is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 -#include -#endif -#include "dns_conf.h" -#include "tlog.h" -#include "util.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UNWIND_BACKTRACE -#include -#endif - -#define TMP_BUFF_LEN_32 32 - -#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 - -#define IPV6_ADDR_LEN 16 -#define IPV4_ADDR_LEN 4 - -#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 -#define PACKET_BUF_SIZE 8192 -#define PACKET_MAGIC 0X11040918 - -struct ipset_netlink_attr { - unsigned short len; - unsigned short type; -}; - -struct ipset_netlink_msg { - unsigned char family; - unsigned char version; - __be16 res_id; -}; - -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 ipset_fd; -static int pidfile_fd; -static int daemon_fd; -static int netlink_neighbor_fd; - -unsigned long get_tick_count(void) -{ - struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - - return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000); -} - -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; -} - -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 *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 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; -} - -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 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 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; -} - -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; -} - -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(0); - - 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); -} - -int netlink_get_neighbors(int family, - 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(&msg, 0, sizeof(msg)); - 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 | NLM_F_DUMP; - nlh->nlmsg_seq = time(NULL); - nlh->nlmsg_pid = getpid(); - - ndm = NLMSG_DATA(nlh); - ndm->ndm_family = family; - - while (true) { - 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 (true) { - 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; -} - -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 create_pid_file(const char *pid_file) -{ - int fd = 0; - int flags = 0; - char buff[TMP_BUFF_LEN_32]; - - /* 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; - } - - 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; -} - -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; -} - -int generate_cert_key(const char *key_path, const char *cert_path, const char *san, int days) -{ - int ret = -1; -#if (OPENSSL_VERSION_NUMBER <= 0x30000000L) - RSA *rsa = NULL; - BIGNUM *bn = NULL; -#endif - X509_EXTENSION *cert_ext = NULL; - BIO *cert_file = NULL; - BIO *key_file = NULL; - X509 *cert = NULL; - EVP_PKEY *pkey = NULL; - const int RSA_KEY_LENGTH = 2048; - - if (key_path == NULL || cert_path == NULL) { - return ret; - } - - key_file = BIO_new_file(key_path, "wb"); - cert_file = BIO_new_file(cert_path, "wb"); - cert = X509_new(); - if (cert == NULL) { - goto out; - } - - X509_set_version(cert, 2); - -#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) - pkey = EVP_RSA_gen(RSA_KEY_LENGTH); -#else - bn = BN_new(); - rsa = RSA_new(); - pkey = EVP_PKEY_new(); - if (rsa == NULL || pkey == NULL || bn == NULL) { - goto out; - } - - EVP_PKEY_assign(pkey, EVP_PKEY_RSA, rsa); - BN_set_word(bn, RSA_F4); - if (RSA_generate_key_ex(rsa, RSA_KEY_LENGTH, bn, NULL) != 1) { - goto out; - } -#endif - - if (key_file == NULL || cert_file == NULL || cert == NULL || pkey == NULL) { - goto out; - } - - ASN1_INTEGER_set(X509_get_serialNumber(cert), 1); // serial number - X509_gmtime_adj(X509_get_notBefore(cert), 0); // now - X509_gmtime_adj(X509_get_notAfter(cert), days * 24 * 3600); // accepts secs - - X509_set_pubkey(cert, pkey); - - X509_NAME *name = X509_get_subject_name(cert); - - const unsigned char *country = (unsigned char *)"smartdns"; - const unsigned char *company = (unsigned char *)"smartdns"; - const unsigned char *common_name = (unsigned char *)"smartdns"; - - X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, country, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, company, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, common_name, -1, -1, 0); - - if (san != NULL) { - cert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, san); - if (cert_ext == NULL) { - goto out; - } - ret = X509_add_ext(cert, cert_ext, -1); - } - - X509_set_issuer_name(cert, name); - - // Add X509v3 extensions - cert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_basic_constraints, "CA:FALSE"); - ret = X509_add_ext(cert, cert_ext, -1); - X509_EXTENSION_free(cert_ext); - - cert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, "digitalSignature,keyEncipherment"); - 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, pkey, EVP_sha256()); - - ret = PEM_write_bio_PrivateKey(key_file, pkey, NULL, NULL, 0, NULL, NULL); - if (ret != 1) { - goto out; - } - - ret = PEM_write_bio_X509(cert_file, cert); - if (ret != 1) { - goto out; - } - - chmod(key_path, S_IRUSR); - chmod(cert_path, S_IRUSR); - - ret = 0; -out: - if (cert_ext) { - X509_EXTENSION_free(cert_ext); - } - - if (pkey) { - EVP_PKEY_free(pkey); - } - -#if (OPENSSL_VERSION_NUMBER <= 0x30000000L) - if (rsa && pkey == NULL) { - RSA_free(rsa); - } - - if (bn) { - BN_free(bn); - } -#endif - - if (cert_file) { - BIO_free_all(cert_file); - } - - if (key_file) { - BIO_free_all(key_file); - } - - if (cert) { - X509_free(cert); - } - - return ret; -} - -#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 - -#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; -} - -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; -} - -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; -} - -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; -} - -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; -} - -#ifdef HAVE_UNWIND_BACKTRACE - -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 - -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 (true) { - sleep(1); - }; -} - -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; -} - -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 (true); - - 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 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 dns_is_quic_supported(void) -{ -#ifdef OSSL_QUIC1_VERSION - return 1; -#else - return 0; -#endif -} - -#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 = 0; - 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_HTTPS: { - char name[DNS_MAX_CNAME_LEN] = {0}; - char target[DNS_MAX_CNAME_LEN] = {0}; - struct dns_https_param *p = NULL; - int priority = 0; - - ret = dns_get_HTTPS_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_get_HTTPS_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 diff --git a/src/utils/bloom.c b/src/utils/bloom.c new file mode 100755 index 0000000000..48ad283571 --- /dev/null +++ b/src/utils/bloom.c @@ -0,0 +1,112 @@ +#include "smartdns/bloom.h" +#include +#include + +// FNV-1a 哈希函数常量 +#define FNV_PRIME_32 16777619 +#define FNV_OFFSET_BASIS_32 2166136261U + +// 第一个哈希函数 (FNV-1a) +static uint32_t hash_fnv1a(const void *data, size_t len) { + const unsigned char *p = (const unsigned char *)data; + uint32_t hash = FNV_OFFSET_BASIS_32; + for (size_t i = 0; i < len; ++i) { + hash ^= (uint32_t)p[i]; + hash *= FNV_PRIME_32; + } + return hash; +} + +// 第二个哈希函数 (基于 FNV-1a 和简单的位移) +// 注意: 这种方式生成的哈希函数独立性可能不够好,仅作示例。 +// 在实际应用中,更推荐使用像 MurmurHash3 这样为生成多个独立哈希值设计的算法, +// 或者使用不同的种子多次调用同一个高质量哈希函数。 +static uint32_t hash_fnv1a_seeded(const void *data, size_t len, uint32_t seed) { + const unsigned char *p = (const unsigned char *)data; + uint32_t hash = FNV_OFFSET_BASIS_32 ^ seed; // Incorporate seed + for (size_t i = 0; i < len; ++i) { + hash ^= (uint32_t)p[i]; + hash *= FNV_PRIME_32; + } + return hash; +} + + +bloom_filter_t *bloom_filter_new(size_t size, size_t num_hashes) { + if (size == 0 || num_hashes == 0) { + return NULL; + } + bloom_filter_t *bf = (bloom_filter_t *)malloc(sizeof(bloom_filter_t)); + if (!bf) { + return NULL; + } + // 位数组大小向上取整到字节 + bf->bit_array = (uint8_t *)calloc((size + 7) / 8, sizeof(uint8_t)); + if (!bf->bit_array) { + free(bf); + return NULL; + } + bf->size = size; + bf->num_hashes = num_hashes; + return bf; +} + +void bloom_filter_free(bloom_filter_t *bf) { + if (bf) { + free(bf->bit_array); + free(bf); + } +} + +// 内部函数,用于设置位数组中的某一位 +static inline void set_bit(uint8_t *bit_array, size_t bit_index) { + bit_array[bit_index / 8] |= (1 << (bit_index % 8)); +} + +// 内部函数,用于检查位数组中的某一位是否被设置 +static inline int get_bit(const uint8_t *bit_array, size_t bit_index) { + return (bit_array[bit_index / 8] & (1 << (bit_index % 8))) != 0; +} + +void bloom_filter_add(bloom_filter_t *bf, const void *item, size_t item_len) { + if (!bf || !item || item_len == 0) { + return; + } + uint32_t hash1 = hash_fnv1a(item, item_len); + uint32_t hash2 = hash_fnv1a_seeded(item, item_len, hash1); // 使用 hash1 作为第二个哈希的种子,增加变化 + + for (size_t i = 0; i < bf->num_hashes; ++i) { + // Kirsch-Mitzenmacher 优化:使用两个哈希函数生成 k 个哈希值 + // h_i(x) = (h1(x) + i * h2(x)) % m + // 其中 m 是布隆过滤器的大小 (bf->size) + // 注意:如果 h2(x) 是0,可能会导致所有哈希值都相同或聚集。 + // 一个更健壮的方法是确保 h2(x) 不为0,或者使用其他生成 k 个哈希值的方法。 + // 为简单起见,这里假设 h2 不太可能为0。 + uint32_t combined_hash = hash1 + (uint32_t)i * hash2; + if (hash2 == 0 && i > 0) { // 简单的保护,如果h2是0,后续哈希会相同 + combined_hash = hash1 + (uint32_t)i * (hash1 >> 16 | 1); // 引入一些变化 + } + size_t bit_to_set = combined_hash % bf->size; + set_bit(bf->bit_array, bit_to_set); + } +} + +int bloom_filter_check(bloom_filter_t *bf, const void *item, size_t item_len) { + if (!bf || !item || item_len == 0) { + return 0; // 通常表示不在集合中或无效输入 + } + uint32_t hash1 = hash_fnv1a(item, item_len); + uint32_t hash2 = hash_fnv1a_seeded(item, item_len, hash1); + + for (size_t i = 0; i < bf->num_hashes; ++i) { + uint32_t combined_hash = hash1 + (uint32_t)i * hash2; + if (hash2 == 0 && i > 0) { + combined_hash = hash1 + (uint32_t)i * (hash1 >> 16 | 1); + } + size_t bit_to_check = combined_hash % bf->size; + if (!get_bit(bf->bit_array, bit_to_check)) { + return 0; // 如果任何一个位未被设置,则元素肯定不在过滤器中 + } + } + return 1; // 所有相关位都被设置,元素可能在过滤器中 +} \ No newline at end of file diff --git a/src/utils/capbility.c b/src/utils/capbility.c new file mode 100755 index 0000000000..c0db03f579 --- /dev/null +++ b/src/utils/capbility.c @@ -0,0 +1,114 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/utils/daemon.c b/src/utils/daemon.c new file mode 100755 index 0000000000..f7edabd2ce --- /dev/null +++ b/src/utils/daemon.c @@ -0,0 +1,318 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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]; + + /* 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; + } + + 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; +} \ No newline at end of file diff --git a/src/utils/dns_debug.c b/src/utils/dns_debug.c new file mode 100755 index 0000000000..6951d8c919 --- /dev/null +++ b/src/utils/dns_debug.c @@ -0,0 +1,461 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = 0; + 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_HTTPS: { + char name[DNS_MAX_CNAME_LEN] = {0}; + char target[DNS_MAX_CNAME_LEN] = {0}; + struct dns_https_param *p = NULL; + int priority = 0; + + ret = dns_get_HTTPS_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_get_HTTPS_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 diff --git a/src/utils/ipset.c b/src/utils/ipset.c new file mode 100755 index 0000000000..fda3a8c5f0 --- /dev/null +++ b/src/utils/ipset.c @@ -0,0 +1,191 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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(0); + + 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); +} \ No newline at end of file diff --git a/src/utils/misc.c b/src/utils/misc.c new file mode 100755 index 0000000000..6fc280bda1 --- /dev/null +++ b/src/utils/misc.c @@ -0,0 +1,295 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; + } +} diff --git a/src/utils/neighbors.c b/src/utils/neighbors.c new file mode 100755 index 0000000000..d2bd6edc74 --- /dev/null +++ b/src/utils/neighbors.c @@ -0,0 +1,185 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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, + 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(&msg, 0, sizeof(msg)); + 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 | NLM_F_DUMP; + nlh->nlmsg_seq = time(NULL); + nlh->nlmsg_pid = getpid(); + + ndm = NLMSG_DATA(nlh); + ndm->ndm_family = family; + + 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; +} \ No newline at end of file diff --git a/src/utils/net.c b/src/utils/net.c new file mode 100755 index 0000000000..65101a4222 --- /dev/null +++ b/src/utils/net.c @@ -0,0 +1,490 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/src/utils/nftset.c b/src/utils/nftset.c new file mode 100755 index 0000000000..312641e018 --- /dev/null +++ b/src/utils/nftset.c @@ -0,0 +1,609 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = 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 = 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 = 0; + + 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; + + if (_nftset_socket_init() != 0) { + return -1; + } + + /* clear pending error message*/ + for (;;) { + uint8_t buff[1024]; + ret = recv(nftset_fd, buff, sizeof(buff), MSG_DONTWAIT); + if (ret < 0) { + break; + } + } + + for (;;) { + len = send(nftset_fd, msg, msg_len, 0); + if (len == msg_len) { + break; + } + + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + struct timespec waiter; + waiter.tv_sec = 0; + waiter.tv_nsec = 10000; + nanosleep(&waiter, NULL); + continue; + } + + 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) { + return -1; + } + + if ((pfds.revents & POLLIN) == 0) { + 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; + } + + return -1; + } + + do_recv = 1; + len += ret; + + 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; + return -1; + } + + continue; + } + + if (nlh->nlmsg_type & (NFNL_SUBSYS_NFTABLES << 8)) { + if (nlh->nlmsg_type & NLMSG_DONE) { + break; + } + } + + errno = ENOTSUP; + 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 = 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; +} + +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, 0, &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) +{ + 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; + int nffamily = _nftset_get_nffamily_from_str(familyname); + + 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) { + if (dns_conf.nftset_debug_enable) { + tlog(TLOG_ERROR, "nftset add failed, family:%s, table:%s, set:%s, error:%s", familyname, tablename, + setname, "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) { + tlog(TLOG_ERROR, "nftset add failed, family:%s, table:%s, set:%s, error:%s", familyname, tablename, setname, + strerror(errno)); + } + + 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 \ No newline at end of file diff --git a/src/utils/ssl.c b/src/utils/ssl.c new file mode 100755 index 0000000000..c08ff3ec5c --- /dev/null +++ b/src/utils/ssl.c @@ -0,0 +1,746 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 = malloc(sizeof(struct DNS_EVP_PKEY_CTX)); + if (ctx == NULL) { + return NULL; + } + memset(ctx, 0, sizeof(struct DNS_EVP_PKEY_CTX)); + + 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 = malloc(sizeof(struct DNS_EVP_PKEY_CTX)); + if (ctx == NULL) { + return NULL; + } + memset(ctx, 0, sizeof(struct DNS_EVP_PKEY_CTX)); + + 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 *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, 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) +{ +#ifdef OSSL_QUIC1_VERSION + return 1; +#else + return 0; +#endif +} \ No newline at end of file diff --git a/src/utils/stack.c b/src/utils/stack.c new file mode 100755 index 0000000000..5bd289a833 --- /dev/null +++ b/src/utils/stack.c @@ -0,0 +1,101 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 +#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 \ No newline at end of file diff --git a/src/utils/tls_header_parse.c b/src/utils/tls_header_parse.c new file mode 100755 index 0000000000..3f1193cd57 --- /dev/null +++ b/src/utils/tls_header_parse.c @@ -0,0 +1,210 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} \ No newline at end of file diff --git a/src/utils/url.c b/src/utils/url.c new file mode 100755 index 0000000000..20d42ce611 --- /dev/null +++ b/src/utils/url.c @@ -0,0 +1,200 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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; +} diff --git a/test/Makefile b/test/Makefile index 969c7f8e05..30056ee6d9 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,5 +1,5 @@ -# Copyright (C) 2018-2024 Ruilin Peng (Nick) . +# Copyright (C) 2018-2025 Ruilin Peng (Nick) . # # smartdns is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/test/cases/test-address.cc b/test/cases/test-address.cc index 95b402face..bb71967ad6 100644 --- a/test/cases/test-address.cc +++ b/test/cases/test-address.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-audit.cc b/test/cases/test-audit.cc new file mode 100644 index 0000000000..dbd56c10c6 --- /dev/null +++ b/test/cases/test-audit.cc @@ -0,0 +1,77 @@ +/************************************************************************* + * + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . + * + * smartdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * smartdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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"; +} diff --git a/test/cases/test-bind.cc b/test/cases/test-bind.cc index 248375708e..769b754714 100644 --- a/test/cases/test-bind.cc +++ b/test/cases/test-bind.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/cases/test-bootstrap.cc b/test/cases/test-bootstrap.cc index 1bc6d9c6b3..c202a56fef 100644 --- a/test/cases/test-bootstrap.cc +++ b/test/cases/test-bootstrap.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-cache.cc b/test/cases/test-cache.cc index de67d26449..fbe65ae923 100644 --- a/test/cases/test-cache.cc +++ b/test/cases/test-cache.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" @@ -27,7 +27,7 @@ #include /* clang-format off */ -#include "dns_cache.h" +#include "smartdns/dns_cache.h" /* clang-format on */ class Cache : public ::testing::Test diff --git a/test/cases/test-client-rule.cc b/test/cases/test-client-rule.cc index be80e6fb76..8bc9278bee 100644 --- a/test/cases/test-client-rule.cc +++ b/test/cases/test-client-rule.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-cname.cc b/test/cases/test-cname.cc index 2664a706ac..7188edd5d1 100644 --- a/test/cases/test-cname.cc +++ b/test/cases/test-cname.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-ddns.cc b/test/cases/test-ddns.cc index 8643ba25ff..cffe1fc51f 100644 --- a/test/cases/test-ddns.cc +++ b/test/cases/test-ddns.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-discard-block-ip.cc b/test/cases/test-discard-block-ip.cc index 25f112198f..3256d33d9a 100644 --- a/test/cases/test-discard-block-ip.cc +++ b/test/cases/test-discard-block-ip.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-dns64.cc b/test/cases/test-dns64.cc index 4a3810d043..fec970d47a 100644 --- a/test/cases/test-dns64.cc +++ b/test/cases/test-dns64.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-domain-rule.cc b/test/cases/test-domain-rule.cc index d588490e7f..66f78f2f26 100644 --- a/test/cases/test-domain-rule.cc +++ b/test/cases/test-domain-rule.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-domain-set.cc b/test/cases/test-domain-set.cc index b92cc58695..7d22bf2670 100644 --- a/test/cases/test-domain-set.cc +++ b/test/cases/test-domain-set.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-dualstack.cc b/test/cases/test-dualstack.cc index f48bcc023b..b8a90f3652 100644 --- a/test/cases/test-dualstack.cc +++ b/test/cases/test-dualstack.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-edns.cc b/test/cases/test-edns.cc index 3be3331f50..fc46439fe3 100644 --- a/test/cases/test-edns.cc +++ b/test/cases/test-edns.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-group.cc b/test/cases/test-group.cc index c2d02e98e2..9c41793ede 100644 --- a/test/cases/test-group.cc +++ b/test/cases/test-group.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" @@ -522,6 +522,65 @@ group-match -domain a.com 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; @@ -556,6 +615,6 @@ server 127.0.0.1:62053 ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); - EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 60); + EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } \ No newline at end of file diff --git a/test/cases/test-hosts.cc b/test/cases/test-hosts.cc index 0355830fb4..1c378c0a9b 100644 --- a/test/cases/test-hosts.cc +++ b/test/cases/test-hosts.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include @@ -148,7 +148,7 @@ hosts-file /tmp/*.hosts ASSERT_EQ(client.GetAnswerNum(), 0); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "c.com"); - EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 60); + EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); ASSERT_TRUE(client.Query("d.com A", 60053)); diff --git a/test/cases/test-http.cc b/test/cases/test-http.cc index 446a825e84..497c23f966 100644 --- a/test/cases/test-http.cc +++ b/test/cases/test-http.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2023 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,11 +17,11 @@ */ #include "client.h" -#include "dns.h" -#include "http_parse.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/dns.h" +#include "smartdns/http_parse.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include @@ -133,7 +133,6 @@ TEST_F(HTTP, http1_1_response_serialize) http_head_destroy(http_head); } - TEST_F(HTTP, http3_0_parse) { struct http_head *http_head = http_head_init(1024, HTTP_VERSION_3_0); @@ -169,4 +168,57 @@ TEST_F(HTTP, http3_0_parse) EXPECT_STREQ(http_head_get_params_value(http_head, "lang"), "cn"); http_head_destroy(http_head); -} \ No newline at end of file +} + +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); +} diff --git a/test/cases/test-https.cc b/test/cases/test-https.cc index ffde4a3135..4acbe8fc16 100644 --- a/test/cases/test-https.cc +++ b/test/cases/test-https.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2023 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include @@ -260,7 +260,7 @@ cache-persist no)"""); ASSERT_EQ(client.GetAuthorityNum(), 1); EXPECT_EQ(client.GetStatus(), "NXDOMAIN"); EXPECT_EQ(client.GetAuthority()[0].GetName(), "a.com"); - EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 60); + EXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3); EXPECT_EQ(client.GetAuthority()[0].GetType(), "SOA"); } diff --git a/test/cases/test-idna.cc b/test/cases/test-idna.cc index d4b4f51865..ad691ef87f 100644 --- a/test/cases/test-idna.cc +++ b/test/cases/test-idna.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-ip-alias.cc b/test/cases/test-ip-alias.cc index e5996629c1..07fce2c332 100644 --- a/test/cases/test-ip-alias.cc +++ b/test/cases/test-ip-alias.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-ip-rule.cc b/test/cases/test-ip-rule.cc index 62607a18de..aacfbe1b71 100644 --- a/test/cases/test-ip-rule.cc +++ b/test/cases/test-ip-rule.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-mdns.cc b/test/cases/test-mdns.cc index c16d7dd59d..3ac7e3581c 100644 --- a/test/cases/test-mdns.cc +++ b/test/cases/test-mdns.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,8 +17,8 @@ */ #include "client.h" -#include "dns.h" -#include "dns_client.h" +#include "smartdns/dns.h" +#include "smartdns/dns_client.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-mock-server.cc b/test/cases/test-mock-server.cc index b715fdc391..11eb8990b4 100644 --- a/test/cases/test-mock-server.cc +++ b/test/cases/test-mock-server.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/cases/test-nameserver.cc b/test/cases/test-nameserver.cc index cd930c9f20..8c00f083d1 100644 --- a/test/cases/test-nameserver.cc +++ b/test/cases/test-nameserver.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-perf.cc b/test/cases/test-perf.cc index 504c1ae513..b77a40ff0c 100644 --- a/test/cases/test-perf.cc +++ b/test/cases/test-perf.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-ping.cc b/test/cases/test-ping.cc index 33e2676403..f6e7bf0e89 100644 --- a/test/cases/test-ping.cc +++ b/test/cases/test-ping.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "fast_ping.h" +#include "smartdns/fast_ping.h" #include "include/utils.h" #include "server.h" -#include "tlog.h" +#include "smartdns/tlog.h" #include "gtest/gtest.h" class Ping : public ::testing::Test diff --git a/test/cases/test-ptr.cc b/test/cases/test-ptr.cc index c13db991f2..e8f3d05cbe 100644 --- a/test/cases/test-ptr.cc +++ b/test/cases/test-ptr.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-qtype-soa.cc b/test/cases/test-qtype-soa.cc index 85663b8e8b..544e032df2 100644 --- a/test/cases/test-qtype-soa.cc +++ b/test/cases/test-qtype-soa.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" diff --git a/test/cases/test-rule.cc b/test/cases/test-rule.cc index 9ca6d6ba12..68c89c80eb 100644 --- a/test/cases/test-rule.cc +++ b/test/cases/test-rule.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-same-pending-query.cc b/test/cases/test-same-pending-query.cc index 8bf36260e2..2833d8ca04 100644 --- a/test/cases/test-same-pending-query.cc +++ b/test/cases/test-same-pending-query.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-server.cc b/test/cases/test-server.cc index 1437325646..2131e69570 100644 --- a/test/cases/test-server.cc +++ b/test/cases/test-server.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" #include "gtest/gtest.h" @@ -331,3 +331,123 @@ server 127.0.0.1:61054 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"); +} diff --git a/test/cases/test-speed-check.cc b/test/cases/test-speed-check.cc index 4b04895064..d555af9a8e 100644 --- a/test/cases/test-speed-check.cc +++ b/test/cases/test-speed-check.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-srv.cc b/test/cases/test-srv.cc index 7ef63dc3e2..292fee5d9d 100644 --- a/test/cases/test-srv.cc +++ b/test/cases/test-srv.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/cases/test-subnet.cc b/test/cases/test-subnet.cc index c36915f3b9..7522ba4b1b 100644 --- a/test/cases/test-subnet.cc +++ b/test/cases/test-subnet.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,10 @@ */ #include "client.h" -#include "dns.h" +#include "smartdns/dns.h" #include "include/utils.h" #include "server.h" -#include "util.h" +#include "smartdns/util.h" #include "gtest/gtest.h" #include diff --git a/test/client.cc b/test/client.cc index e1718fe7e8..c4f6a089d8 100644 --- a/test/client.cc +++ b/test/client.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/client.h b/test/client.h index d816d6362e..ced62eef71 100644 --- a/test/client.h +++ b/test/client.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/include/utils.h b/test/include/utils.h index e47104660b..8503aaa18e 100644 --- a/test/include/utils.h +++ b/test/include/utils.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/server.cc b/test/server.cc index 976b5cff1d..88000fb40d 100644 --- a/test/server.cc +++ b/test/server.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,11 +17,11 @@ */ #include "server.h" -#include "dns_server.h" -#include "fast_ping.h" +#include "smartdns/dns_server.h" +#include "smartdns/fast_ping.h" #include "include/utils.h" -#include "smartdns.h" -#include "util.h" +#include "smartdns/smartdns.h" +#include "smartdns/util.h" #include #include #include diff --git a/test/server.h b/test/server.h index 1e346982e2..d64e90f361 100644 --- a/test/server.h +++ b/test/server.h @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ #ifndef _SMARTDNS_SERVER_ #define _SMARTDNS_SERVER_ -#include "dns.h" -#include "fast_ping.h" +#include "smartdns/dns.h" +#include "smartdns/fast_ping.h" #include "include/utils.h" #include #include diff --git a/test/test.cc b/test/test.cc index 986e03577d..188c89185c 100644 --- a/test/test.cc +++ b/test/test.cc @@ -1,6 +1,6 @@ /************************************************************************* * - * Copyright (C) 2018-2024 Ruilin Peng (Nick) . + * Copyright (C) 2018-2025 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/utils.cc b/test/utils.cc index 03104debd8..3bee10b17a 100644 --- a/test/utils.cc +++ b/test/utils.cc @@ -1,5 +1,5 @@ #include "include/utils.h" -#include "util.h" +#include "smartdns/util.h" #include #include #include