Bläddra i källkod

proxy: simple add socks5 and https proxy support

Nick Peng 2 år sedan
förälder
incheckning
83c4901190
14 ändrade filer med 1828 tillägg och 34 borttagningar
  1. 7 5
      ReadMe.md
  2. 7 5
      ReadMe_en.md
  3. 9 1
      etc/smartdns/smartdns.conf
  4. 1 1
      src/Makefile
  5. 363 10
      src/dns_client.c
  6. 4 3
      src/dns_client.h
  7. 249 1
      src/dns_conf.c
  8. 40 2
      src/dns_conf.h
  9. 2 2
      src/dns_server.c
  10. 993 0
      src/proxy.c
  11. 88 0
      src/proxy.h
  12. 42 1
      src/smartdns.c
  13. 20 2
      src/util.c
  14. 3 1
      src/util.h

+ 7 - 5
ReadMe.md

@@ -103,7 +103,7 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
    支持从域名所属 IP 地址列表中查找到访问速度最快的 IP 地址,并返回给客户端,提高网络访问速度。
 
 3. **支持多种查询协议**  
-   支持 UDP、TCP、DOT 和 DOH 查询,以及非 53 端口查询。
+   支持 UDP、TCP、DOT 和 DOH 查询,以及非 53 端口查询;支持通过socks5,HTTP代理查询
 
 4. **特定域名 IP 地址指定**  
    支持指定域名的 IP 地址,达到广告过滤效果、避免恶意网站的效果。
@@ -590,10 +590,12 @@ entware|ipkg update<br>ipkg install smartdns|软件源路径:https://bin.entwa
 | audit-num | 审计归档个数 | 2 | 大于等于 0 的数字 | audit-num 2 |
 | audit-file-mode | 审计归档文件权限 | 0640 | 文件权限 | log-file-mode 644 |
 | conf-file | 附加配置文件 | 无 | 合法路径字符串 | conf-file /etc/smartdns/smartdns.more.conf |
-| server | 上游 UDP DNS | 无 | 可重复。<br>[ip][:port]:服务器 IP:端口(可选)<br>[-blacklist-ip]:配置 IP 过滤结果。<br>[-whitelist-ip]:指定仅接受参数中配置的 IP 范围<br>[-group [group] ...]:DNS 服务器所属组,比如 office 和 foreign,和 nameserver 配套使用<br>[-exclude-default-group]:将 DNS 服务器从默认组中排除。<br>[-set-mark]:设置数据包标记so-mark| server 8.8.8.8:53 -blacklist-ip -group g1 |
-| server-tcp | 上游 TCP DNS | 无 | 可重复。<br>[ip][:port]:服务器 IP:端口(可选)<br>[-blacklist-ip]:配置 IP 过滤结果<br>[-whitelist-ip]:指定仅接受参数中配置的 IP 范围。<br>[-group [group] ...]:DNS 服务器所属组,比如 office 和 foreign,和 nameserver 配套使用<br>[-exclude-default-group]:将 DNS 服务器从默认组中排除。<br>[-set-mark]:设置数据包标记so-mark | server-tcp 8.8.8.8:53 |
-| server-tls | 上游 TLS DNS | 无 | 可重复。<br>[ip][:port]:服务器 IP:端口(可选)<br>[-spki-pin [sha256-pin]]:TLS 合法性校验 SPKI 值,base64 编码的 sha256 SPKI pin 值<br>[-host-name]:TLS SNI 名称, 名称设置为-,表示停用SNI名称<br>[-tls-host-verify]:TLS 证书主机名校验<br> [-no-check-certificate]:跳过证书校验<br>[-blacklist-ip]:配置 IP 过滤结果<br>[-whitelist-ip]:仅接受参数中配置的 IP 范围<br>[-group [group] ...]:DNS 服务器所属组,比如 office 和 foreign,和 nameserver 配套使用<br>[-exclude-default-group]:将 DNS 服务器从默认组中排除。<br>[-set-mark]:设置数据包标记so-mark | server-tls 8.8.8.8:853 |
-| server-https | 上游 HTTPS DNS | 无 | 可重复。<br>https://[host][:port]/path:服务器 IP:端口(可选)<br>[-spki-pin [sha256-pin]]:TLS 合法性校验 SPKI 值,base64 编码的 sha256 SPKI pin 值<br>[-host-name]:TLS SNI 名称<br>[-http-host]:http 协议头主机名<br>[-tls-host-verify]:TLS 证书主机名校验<br> [-no-check-certificate]:跳过证书校验<br>[-blacklist-ip]:配置 IP 过滤结果<br>[-whitelist-ip]:仅接受参数中配置的 IP 范围。<br>[-group [group] ...]:DNS 服务器所属组,比如 office 和 foreign,和 nameserver 配套使用<br>[-exclude-default-group]:将 DNS 服务器从默认组中排除。<br>[-set-mark]:设置数据包标记so-mark | server-https https://cloudflare-dns.com/dns-query |
+| server | 上游 UDP DNS | 无 | 可重复。<br>[ip][:port]:服务器 IP:端口(可选)<br>[-blacklist-ip]:配置 IP 过滤结果。<br>[-whitelist-ip]:指定仅接受参数中配置的 IP 范围<br>[-group [group] ...]:DNS 服务器所属组,比如 office 和 foreign,和 nameserver 配套使用<br>[-exclude-default-group]:将 DNS 服务器从默认组中排除。<br>[-set-mark mark]:设置数据包标记so-mark。<br>[-proxy name]:设置代理服务器。 | server 8.8.8.8:53 -blacklist-ip -group g1 -proxy proxy|
+| server-tcp | 上游 TCP DNS | 无 | 可重复。<br>[ip][:port]:服务器 IP:端口(可选)<br>[-blacklist-ip]:配置 IP 过滤结果<br>[-whitelist-ip]:指定仅接受参数中配置的 IP 范围。<br>[-group [group] ...]:DNS 服务器所属组,比如 office 和 foreign,和 nameserver 配套使用<br>[-exclude-default-group]:将 DNS 服务器从默认组中排除。<br>[-set-mark mark]:设置数据包标记so-mark。<br>[-proxy name]:设置代理服务器。 | server-tcp 8.8.8.8:53 |
+| server-tls | 上游 TLS DNS | 无 | 可重复。<br>[ip][:port]:服务器 IP:端口(可选)<br>[-spki-pin [sha256-pin]]:TLS 合法性校验 SPKI 值,base64 编码的 sha256 SPKI pin 值<br>[-host-name]:TLS SNI 名称, 名称设置为-,表示停用SNI名称<br>[-tls-host-verify]:TLS 证书主机名校验<br> [-no-check-certificate]:跳过证书校验<br>[-blacklist-ip]:配置 IP 过滤结果<br>[-whitelist-ip]:仅接受参数中配置的 IP 范围<br>[-group [group] ...]:DNS 服务器所属组,比如 office 和 foreign,和 nameserver 配套使用<br>[-exclude-default-group]:将 DNS 服务器从默认组中排除。<br>[-set-mark mark]:设置数据包标记so-mark。<br>[-proxy name]:设置代理服务器。 | server-tls 8.8.8.8:853 |
+| server-https | 上游 HTTPS DNS | 无 | 可重复。<br>https://[host][:port]/path:服务器 IP:端口(可选)<br>[-spki-pin [sha256-pin]]:TLS 合法性校验 SPKI 值,base64 编码的 sha256 SPKI pin 值<br>[-host-name]:TLS SNI 名称<br>[-http-host]:http 协议头主机名<br>[-tls-host-verify]:TLS 证书主机名校验<br> [-no-check-certificate]:跳过证书校验<br>[-blacklist-ip]:配置 IP 过滤结果<br>[-whitelist-ip]:仅接受参数中配置的 IP 范围。<br>[-group [group] ...]:DNS 服务器所属组,比如 office 和 foreign,和 nameserver 配套使用<br>[-exclude-default-group]:将 DNS 服务器从默认组中排除。<br>[-set-mark]:设置数据包标记so-mark。<br>[-proxy name]:设置代理服务器。 | server-https https://cloudflare-dns.com/dns-query |
+| proxy-socks5 | socks5代理服务器 | 无 | 可重复。proxy-socks5 ip:port <br>[-name]: 代理服务器名称。<br>[-u\|-user]:用户名。<br>[-p\|-password]:密码。|proxy-socks5 1.2.3.4:1080 -name proxy|
+| proxy-http | http代理服务器 | 无 | 可重复。proxy-http ip:port <br>[-name]: 代理服务器名称。<br>[-u\|-user]:用户名。<br>[-p\|-password]:密码。|proxy-http 1.2.3.4:1080 -name proxy|
 | speed-check-mode | 测速模式选择 | 无 | [ping\|tcp:[80]\|none] | speed-check-mode ping,tcp:80,tcp:443 |
 | response-mode | 首次查询响应模式 | first-ping |模式:[fisrt-ping\|fastest-ip\|fastest-response]<br> [first-ping]: 最快ping响应地址模式,DNS上游最快查询时延+ping时延最短,查询等待与链接体验最佳;<br>[fastest-ip]: 最快IP地址模式,查询到的所有IP地址中ping最短的IP。需等待IP测速; <br>[fastest-response]: 最快响应的DNS结果,DNS查询等待时间最短,返回的IP地址可能不是最快。| response-mode first-ping |
 | address | 指定域名 IP 地址 | 无 | address /domain/[ip\|-\|-4\|-6\|#\|#4\|#6] <br>- 表示忽略 <br># 表示返回 SOA <br>4 表示 IPv4 <br>6 表示 IPv6 | address /www.example.com/1.2.3.4 |

+ 7 - 5
ReadMe_en.md

@@ -101,7 +101,7 @@ From the comparison, smartdns found the fastest IP address to visit www.baidu.co
    Supports 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.
 
 3. **Support for multiple query protocols**  
-   Support UDP, TCP, DOT(DNS over TLS), DOH(DNS over HTTPS) queries, and non-53 port queries, effectively avoiding DNS pollution and protect privacy.
+   Support UDP, TCP, DOT(DNS over TLS), DOH(DNS over HTTPS) queries, and non-53 port queries, effectively avoiding DNS pollution and protect privacy, and support query DNS over socks5, http proxy.
 
 4. **Domain IP address specification**  
    Support configuring IP address of specific domain to achieve the effect of advertising filtering, and avoid malicious websites.
@@ -552,10 +552,12 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
 |audit-num|archived audit log number|2|Integer, 0 means turn off the log|audit-num 2
 |audit-file-mode|archived audit log file mode|0640|Integer|audit-file-mode 644
 |conf-file|additional conf file|None|File path|conf-file /etc/smartdns/smartdns.more.conf
-|server|Upstream UDP DNS server|None|Repeatable <br>`[ip][:port]`: Server IP, port optional. <br>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <br>`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted. <br>`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver. <br>`[-exclude-default-group]`: Exclude DNS servers from the default group. <br>`[-set-mark]`:set mark on packets | server 8.8.8.8:53 -blacklist-ip
-|server-tcp|Upstream TCP DNS server|None|Repeatable <br>`[ip][:port]`: Server IP, port optional. <br>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <br>`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted. <br>`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver. <br>`[-exclude-default-group]`: Exclude DNS servers from the default group <br>`[-set-mark]`:set mark on packets | server-tcp 8.8.8.8:53
-|server-tls|Upstream TLS DNS server|None|Repeatable <br>`[ip][:port]`: Server IP, port optional. <br>`[-spki-pin [sha256-pin]]`: TLS verify SPKI value, a base64 encoded SHA256 hash<br>`[-host-name]`:TLS Server name. `-` to disable SNI name.<br>`[-tls-host-verify]`: TLS cert hostname to verify. <br>`-no-check-certificate:`: No check certificate. <br>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <br>`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted. <br>`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver. <br>`[-exclude-default-group]`: Exclude DNS servers from the default group <br> `[-set-mark]`:set mark on packets | server-tls 8.8.8.8:853
-|server-https|Upstream HTTPS DNS server|None|Repeatable <br>`https://[host][:port]/path`: Server IP, port optional. <br>`[-spki-pin [sha256-pin]]`: TLS verify SPKI value, a base64 encoded SHA256 hash<br>`[-host-name]`:TLS Server name<br>`[-http-host]`:http header host. <br>`[-tls-host-verify]`: TLS cert hostname to verify. <br>`-no-check-certificate:`: No check certificate. <br>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <br>`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted. <br>`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver. <br>`[-exclude-default-group]`: Exclude DNS servers from the default group <br> `[-set-mark]`:set mark on packets | server-https https://cloudflare-dns.com/dns-query
+|server|Upstream UDP DNS server|None|Repeatable <br>`[ip][:port]`: Server IP, port optional. <br>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <br>`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted. <br>`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver. <br>`[-exclude-default-group]`: Exclude DNS servers from the default group. <br>`[-set-mark mark]`:set mark on packets <br> `[-proxy name]`: set proxy server| server 8.8.8.8:53 -blacklist-ip
+|server-tcp|Upstream TCP DNS server|None|Repeatable <br>`[ip][:port]`: Server IP, port optional. <br>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <br>`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted. <br>`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver. <br>`[-exclude-default-group]`: Exclude DNS servers from the default group <br>`[-set-mark mark]`:set mark on packets <br> `[-proxy name]`: set proxy server| server-tcp 8.8.8.8:53
+|server-tls|Upstream TLS DNS server|None|Repeatable <br>`[ip][:port]`: Server IP, port optional. <br>`[-spki-pin [sha256-pin]]`: TLS verify SPKI value, a base64 encoded SHA256 hash<br>`[-host-name]`:TLS Server name. `-` to disable SNI name.<br>`[-tls-host-verify]`: TLS cert hostname to verify. <br>`-no-check-certificate:`: No check certificate. <br>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <br>`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted. <br>`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver. <br>`[-exclude-default-group]`: Exclude DNS servers from the default group <br> `[-set-mark mark]`:set mark on packets <br> `[-proxy name]`: set proxy server| server-tls 8.8.8.8:853
+|server-https|Upstream HTTPS DNS server|None|Repeatable <br>`https://[host][:port]/path`: Server IP, port optional. <br>`[-spki-pin [sha256-pin]]`: TLS verify SPKI value, a base64 encoded SHA256 hash<br>`[-host-name]`:TLS Server name<br>`[-http-host]`:http header host. <br>`[-tls-host-verify]`: TLS cert hostname to verify. <br>`-no-check-certificate:`: No check certificate. <br>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <br>`[-whitelist-ip]`: whitelist-ip parameter specifies that only the IP range configured in whitelist-ip is accepted. <br>`[-group [group] ...]`: The group to which the DNS server belongs, such as office, foreign, use with nameserver. <br>`[-exclude-default-group]`: Exclude DNS servers from the default group <br> `[-set-mark mark]`:set mark on packets <br> `[-proxy name]`: set proxy server| server-https https://cloudflare-dns.com/dns-query
+|proxy-socks5| socks5 proxy server | None | Repeatable. <br>`proxy-socks5 ip:po`rt <br>[-name]:  proxy server name. <br>[-u\|-user]:user name. <br>[-p\|-password]:password. |proxy-socks5 1.2.3.4:1080 -name proxy|
+|proxy-http|http proxy server | None | Repeatable. <br>`proxy-http ip:port` <br>[-name]:  proxy server name. <br>[-u\|-user]:user name. <br>[-p\|-password]:password. |proxy-http 1.2.3.4:1080 -name proxy|
 |speed-check-mode|Speed ​​mode|None|[ping\|tcp:[80]\|none]|speed-check-mode ping,tcp:80,tcp:443
 |response-mode|First query response mode|first-ping|Mode: [fisrt-ping\|fastest-ip\|fastest-response]<br> [first-ping]: The fastest dns + ping response mode, DNS query delay + ping delay is the shortest;<br>[fastest-ip]: The fastest IP address mode, return the fastest ip address, may take some time to test speed. <br>[fastest-response]: The fastest response DNS result mode, the DNS query waiting time is the shortest. | response-mode first-ping |
 |address|Domain IP address|None|address /domain/[ip\|-\|-4\|-6\|#\|#4\|#6], `-` for ignore, `#` for return SOA, `4` for IPV4, `6` for IPV6| address /www.example.com/1.2.3.4

+ 9 - 1
etc/smartdns/smartdns.conf

@@ -48,7 +48,7 @@ bind [::]:53
 # dns cache size
 # cache-size [number]
 #   0: for no cache
-cache-size 16384
+cache-size 32768
 
 # enable persist cache when restart
 # cache-persist yes
@@ -170,6 +170,7 @@ log-level info
 #   -check-edns: result must exist edns RR, or discard result.
 #   -group [group]: set server to group, use with nameserver /domain/group.
 #   -exclude-default-group: exclude this server from default group.
+#   -proxy [proxy-name]: use proxy to connect to server.
 # server 8.8.8.8 -blacklist-ip -check-edns -group g1 -group g2
 
 # remote tcp dns server list
@@ -183,6 +184,7 @@ log-level info
 #   -tls-host-verify: cert hostname to verify.
 #   -host-name: TLS sni hostname.
 #   -no-check-certificate: no check certificate.
+#   -proxy [proxy-name]: use proxy to connect to server.
 # Get SPKI with this command:
 #    echo | openssl s_client -connect '[ip]:853' | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
 # default port is 853
@@ -196,9 +198,15 @@ log-level info
 #   -host-name: TLS sni hostname.
 #   -http-host: http host.
 #   -no-check-certificate: no check certificate.
+#   -proxy [proxy-name]: use proxy to connect to server.
 # default port is 443
 # server-https https://cloudflare-dns.com/dns-query
 
+# socks5 and http proxy list
+# proxy-http ip[:port] -name [proxy name] [-u|-user username] [-p|-password password]
+# proxy-socks5 ip[:port] -name [proxy name] [-u|-user username] [-p|-password password]
+# proxy-socks5 127.0.0.1:3328 -name proxy-socks5 -u username -p password
+
 # specific nameserver to domain
 # nameserver /domain/[group|-]
 # nameserver /www.example.com/office, Set the domain name to use the appropriate server group.

+ 1 - 1
src/Makefile

@@ -16,7 +16,7 @@
 
 BIN=smartdns 
 OBJS_LIB=lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o lib/conf.o lib/nftset.o
-OBJS=smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_conf.o dns_cache.o http_parse.o $(OBJS_LIB)
+OBJS=smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_conf.o dns_cache.o http_parse.o proxy.o $(OBJS_LIB)
 
 # cflags
 ifndef CFLAGS

+ 363 - 10
src/dns_client.c

@@ -26,6 +26,7 @@
 #include "hashtable.h"
 #include "http_parse.h"
 #include "list.h"
+#include "proxy.h"
 #include "tlog.h"
 #include "util.h"
 #include <arpa/inet.h>
@@ -91,6 +92,7 @@ struct dns_server_info {
 
 	char ip[DNS_HOSTNAME_LEN];
 	int port;
+	char proxy_name[DNS_HOSTNAME_LEN];
 	/* server type */
 	dns_server_type_t type;
 	long long so_mark;
@@ -103,6 +105,9 @@ struct dns_server_info {
 	int ssl_write_len;
 	SSL_CTX *ssl_ctx;
 	SSL_SESSION *ssl_session;
+
+	struct proxy_conn *proxy;
+
 	pthread_mutex_t lock;
 	char skip_check_cert;
 	dns_server_status status;
@@ -254,6 +259,8 @@ static LIST_HEAD(pending_servers);
 static pthread_mutex_t pending_server_mutex = PTHREAD_MUTEX_INITIALIZER;
 static int dns_client_has_bootstrap_dns = 0;
 
+static int _dns_client_send_udp(struct dns_server_info *server_info, void *packet, int len);
+
 static ssize_t _ssl_read(struct dns_server_info *server, void *buff, int num)
 {
 	ssize_t ret = 0;
@@ -1047,6 +1054,7 @@ static int _dns_client_server_add(char *server_ip, char *server_host, int port,
 	server_info->skip_check_cert = skip_check_cert;
 	server_info->prohibit = 0;
 	server_info->so_mark = flags->set_mark;
+	safe_strncpy(server_info->proxy_name, flags->proxyname, sizeof(server_info->proxy_name));
 	pthread_mutex_init(&server_info->lock, NULL);
 	memcpy(&server_info->flags, flags, sizeof(server_info->flags));
 
@@ -1141,7 +1149,13 @@ static void _dns_client_close_socket(struct dns_server_info *server_info)
 
 	/* remove fd from epoll */
 	epoll_ctl(client.epoll_fd, EPOLL_CTL_DEL, server_info->fd, NULL);
-	close(server_info->fd);
+
+	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;
@@ -1670,6 +1684,69 @@ static int _dns_client_recv(struct dns_server_info *server_info, unsigned char *
 	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);
+	if (proxy == NULL) {
+		tlog(TLOG_ERROR, "create proxy failed, %s", server_info->ip);
+		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));
+		}
+	}
+
+	set_fd_nonblock(fd, 1);
+	set_sock_keepalive(fd, 15, 3, 4);
+
+	ret = proxy_conn_connect(proxy);
+	if (ret != 0) {
+		if (errno == ENETUNREACH) {
+			tlog(TLOG_DEBUG, "connect %s failed, %s", server_info->ip, strerror(errno));
+			goto errout;
+		}
+
+		if (errno != EINPROGRESS) {
+			tlog(TLOG_ERROR, "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;
@@ -1679,6 +1756,10 @@ static int _dns_client_create_socket_udp(struct dns_server_info *server_info)
 	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));
@@ -1750,8 +1831,20 @@ static int _DNS_client_create_socket_tcp(struct dns_server_info *server_info)
 	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);
+		if (proxy == NULL) {
+			tlog(TLOG_ERROR, "create proxy failed, %s", server_info->ip);
+			goto errout;
+		}
+		fd = proxy_conn_get_fd(proxy);
+	} else {
+		fd = socket(server_info->ai_family, SOCK_STREAM, 0);
+	}
 
-	fd = socket(server_info->ai_family, SOCK_STREAM, 0);
 	if (fd < 0) {
 		tlog(TLOG_ERROR, "create socket failed, %s", strerror(errno));
 		goto errout;
@@ -1781,8 +1874,14 @@ static int _DNS_client_create_socket_tcp(struct dns_server_info *server_info)
 	setsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, &yes, sizeof(yes));
 	set_sock_keepalive(fd, 15, 3, 4);
 
-	if (connect(fd, &server_info->addr, server_info->ai_addrlen) != 0) {
-		if (errno == ENETUNREACH || errno == EHOSTUNREACH) {
+	if (proxy) {
+		ret = proxy_conn_connect(proxy);
+	} else {
+		ret = connect(fd, &server_info->addr, server_info->ai_addrlen);
+	}
+
+	if (ret != 0) {
+		if (errno == ENETUNREACH || errno == EHOSTUNREACH || errno == ECONNREFUSED) {
 			tlog(TLOG_DEBUG, "connect %s failed, %s", server_info->ip, strerror(errno));
 			goto errout;
 		}
@@ -1795,6 +1894,7 @@ static int _DNS_client_create_socket_tcp(struct dns_server_info *server_info)
 
 	server_info->fd = fd;
 	server_info->status = DNS_SERVER_STATUS_CONNECTING;
+	server_info->proxy = proxy;
 
 	memset(&event, 0, sizeof(event));
 	event.events = EPOLLIN | EPOLLOUT;
@@ -1814,9 +1914,14 @@ errout:
 
 	server_info->status = DNS_SERVER_STATUS_INIT;
 
-	if (fd > 0) {
+	if (fd > 0 && proxy == NULL) {
 		close(fd);
 	}
+
+	if (proxy) {
+		proxy_conn_free(proxy);
+	}
+
 	return -1;
 }
 
@@ -1825,22 +1930,35 @@ static int _DNS_client_create_socket_tls(struct dns_server_info *server_info, ch
 	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);
+		if (proxy == NULL) {
+			tlog(TLOG_ERROR, "create proxy failed, %s", server_info->ip);
+			goto errout;
+		}
+		fd = proxy_conn_get_fd(proxy);
+	} else {
+		fd = socket(server_info->ai_family, SOCK_STREAM, 0);
+	}
+
 	ssl = SSL_new(server_info->ssl_ctx);
 	if (ssl == NULL) {
 		tlog(TLOG_ERROR, "new ssl failed, %s", server_info->ip);
 		goto errout;
 	}
 
-	fd = socket(server_info->ai_family, SOCK_STREAM, 0);
 	if (fd < 0) {
 		tlog(TLOG_ERROR, "create socket failed, %s", strerror(errno));
 		goto errout;
@@ -1870,8 +1988,14 @@ static int _DNS_client_create_socket_tls(struct dns_server_info *server_info, ch
 	setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority));
 	setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));
 
-	if (connect(fd, &server_info->addr, server_info->ai_addrlen) != 0) {
-		if (errno == ENETUNREACH || errno == EHOSTUNREACH) {
+	if (proxy) {
+		ret = proxy_conn_connect(proxy);
+	} else {
+		ret = connect(fd, &server_info->addr, server_info->ai_addrlen);
+	}
+
+	if (ret != 0) {
+		if (errno == ENETUNREACH || errno == EHOSTUNREACH || errno == ECONNREFUSED) {
 			tlog(TLOG_DEBUG, "connect %s failed, %s", server_info->ip, strerror(errno));
 			goto errout;
 		}
@@ -1902,6 +2026,7 @@ static int _DNS_client_create_socket_tls(struct dns_server_info *server_info, ch
 	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;
@@ -1925,7 +2050,7 @@ errout:
 
 	server_info->status = DNS_SERVER_STATUS_INIT;
 
-	if (fd > 0) {
+	if (fd > 0 && proxy == NULL) {
 		close(fd);
 	}
 
@@ -1933,6 +2058,10 @@ errout:
 		SSL_free(ssl);
 	}
 
+	if (proxy) {
+		proxy_conn_free(proxy);
+	}
+
 	return -1;
 }
 
@@ -1964,6 +2093,103 @@ static int _dns_client_create_socket(struct dns_server_info *server_info)
 	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) {
+		tlog(TLOG_ERROR, "recvfrom failed, %s\n", 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;
+	}
+
+	tlog(TLOG_DEBUG, "recv udp packet from %s, len: %d",
+		 gethost_by_addr(from_host, sizeof(from_host), (struct sockaddr *)&from), len);
+
+	/* 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;
@@ -1977,6 +2203,10 @@ static int _dns_client_process_udp(struct dns_server_info *server_info, struct e
 	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);
@@ -2685,8 +2915,82 @@ errout:
 	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) {
+		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->status = DNS_SERVER_STATUS_CONNECTED;
+			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;
+		} 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) {
 		/* receive from udp */
 		return _dns_client_process_udp(server_info, event, now);
@@ -2703,19 +3007,68 @@ static int _dns_client_process(struct dns_server_info *server_info, struct epoll
 	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) {
-		return -1;
+		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)

+ 4 - 3
src/dns_client.h

@@ -80,8 +80,8 @@ struct dns_query_options {
 };
 
 /* query domain */
-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);
+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);
 
 void dns_client_exit(void);
 
@@ -102,6 +102,7 @@ struct client_dns_server_flag_https {
 	int spi_len;
 	char hostname[DNS_MAX_CNAME_LEN];
 	char httphost[DNS_MAX_CNAME_LEN];
+	char proxyname[DNS_MAX_CNAME_LEN];
 	char path[DNS_MAX_CNAME_LEN];
 	char tls_host_verify[DNS_MAX_CNAME_LEN];
 	char skip_check_cert;
@@ -112,7 +113,7 @@ struct client_dns_server_flags {
 	unsigned int server_flag;
 	unsigned int result_flag;
 	long long set_mark;
-
+	char proxyname[DNS_MAX_CNAME_LEN];
 	union {
 		struct client_dns_server_flag_udp udp;
 		struct client_dns_server_flag_tls tls;

+ 249 - 1
src/dns_conf.c

@@ -1,6 +1,6 @@
 /*************************************************************************
  *
- * Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
  *
  * smartdns is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -52,6 +52,7 @@ struct dns_domain_set_name_table dns_domain_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;
 
@@ -100,6 +101,10 @@ struct dns_domain_check_orders dns_conf_check_orders = {
 };
 static int dns_has_cap_ping = 0;
 
+/* proxy servers */
+struct dns_proxy_servers dns_conf_proxy_servers[PROXY_MAX_SERVERS];
+int dns_conf_proxy_server_num;
+
 /* logging */
 int dns_conf_log_level = TLOG_ERROR;
 char dns_conf_log_file[DNS_MAX_PATH];
@@ -345,6 +350,101 @@ static void _config_group_table_destroy(void)
 	}
 }
 
+struct dns_proxy_names *dns_server_get_proxy_nams(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_MAX_IPLEN) == 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, DNS_MAX_IPLEN) == 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 int _config_server(int argc, char *argv[], dns_server_type_t type, int default_port)
 {
 	int index = dns_conf_server_num;
@@ -371,6 +471,7 @@ static int _config_server(int argc, char *argv[], dns_server_type_t type, int de
 		{"no-check-certificate", no_argument, NULL, 'N'}, /* do not check certificate */
 		{"tls-host-verify", required_argument, NULL, 'V' }, /* verify tls hostname */
 		{"group", required_argument, NULL, 'g'}, /* add to group */
+		{"proxy", required_argument, NULL, 'P'}, /* proxy server */
 		{"exclude-default-group", no_argument, NULL, 'E'}, /* ecluse this from default group */
 		{"set-mark", required_argument, NULL, 254}, /* set mark */
 		{NULL, no_argument, NULL, 0}
@@ -393,6 +494,7 @@ static int _config_server(int argc, char *argv[], dns_server_type_t type, int de
 	server->hostname[0] = '\0';
 	server->httphost[0] = '\0';
 	server->tls_host_verify[0] = '\0';
+	server->proxyname[0] = '\0';
 	server->set_mark = -1;
 
 	if (type == DNS_SERVER_HTTPS) {
@@ -463,6 +565,14 @@ static int _config_server(int argc, char *argv[], dns_server_type_t type, int de
 			safe_strncpy(server->spki, optarg, DNS_MAX_SPKI_LEN);
 			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 'V': {
 			safe_strncpy(server->tls_host_verify, optarg, DNS_MAX_CNAME_LEN);
 			break;
@@ -1489,6 +1599,141 @@ errout:
 	return 0;
 }
 
+static int _config_proxy_server(int argc, char *argv[], struct dns_proxy_servers **pserver, proxy_type_t type)
+{
+	int index = dns_conf_proxy_server_num;
+	struct dns_proxy_servers *server = NULL;
+	char *servers_name = NULL;
+	int port = -1;
+	char *ip = NULL;
+	int opt = 0;
+	unsigned int server_flag = 0;
+	int use_domain = 0;
+
+	/* clang-format off */
+	static struct option long_options[] = {
+		{"name", required_argument, NULL, 'n'}, 
+		{"use-domain", no_argument, NULL, 'd'},
+		{"user", required_argument, NULL, 'u'},
+		{"password", required_argument, NULL, 'p'},
+		{NULL, no_argument, NULL, 0}
+	};
+	/* clang-format on */
+	if (argc <= 1) {
+		tlog(TLOG_ERROR, "invalid parameter.");
+		return -1;
+	}
+
+	ip = argv[1];
+	if (index >= PROXY_MAX_SERVERS) {
+		tlog(TLOG_WARN, "exceeds max server number, %s", ip);
+		return 0;
+	}
+
+	server = malloc(sizeof(*server));
+	if (server == NULL) {
+		tlog(TLOG_WARN, "malloc memory failed.");
+		return -1;
+	}
+	memset(server, 0, sizeof(*server));
+
+	/* process extra options */
+	optind = 1;
+	while (1) {
+		opt = getopt_long_only(argc, argv, "n:du:p:", long_options, NULL);
+		if (opt == -1) {
+			break;
+		}
+
+		switch (opt) {
+		case 'n': {
+			servers_name = optarg;
+			break;
+		}
+		case 'd': {
+			use_domain = 1;
+			break;
+		}
+		case 'u': {
+			safe_strncpy(server->username, optarg, sizeof(server->username));
+			break;
+		}
+		case 'p': {
+			safe_strncpy(server->password, optarg, sizeof(server->password));
+			break;
+		}
+		default:
+			break;
+		}
+	}
+
+	ip = argv[optind];
+	if (ip) {
+		/* parse ip, port from ip */
+		if (parse_ip(ip, server->server, &port) != 0) {
+			return -1;
+		}
+
+		/* if port is not defined, set port to default 53 */
+		if (port == PORT_NOT_DEFINED) {
+			port = 443;
+		}
+	} else {
+		goto errout;
+	}
+
+	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->server_flag = server_flag;
+	server->use_domain = use_domain;
+	dns_conf_proxy_server_num++;
+	tlog(TLOG_DEBUG, "add proxy server %s, flag: %X", ip, server_flag);
+
+	if (pserver) {
+		*pserver = server;
+	}
+
+	return 0;
+
+errout:
+	if (server) {
+		free(server);
+	}
+
+	return -1;
+}
+
+static int _config_proxy_socks5(void *data, int argc, char *argv[])
+{
+	struct dns_proxy_servers *server = NULL;
+	int ret = _config_proxy_server(argc, argv, &server, PROXY_SOCKS5);
+	if (ret == 0) {
+		server->socks5 = 1;
+	}
+	return ret;
+}
+
+static int _config_proxy_http(void *data, int argc, char *argv[])
+{
+	struct dns_proxy_servers *server = NULL;
+	int ret = _config_proxy_server(argc, argv, &server, PROXY_HTTP);
+	if (ret == 0) {
+		server->https = 1;
+	}
+	return ret;
+}
+
 static radix_node_t *_create_addr_node(char *addr)
 {
 	radix_node_t *node = NULL;
@@ -2400,6 +2645,8 @@ static struct config_item _config_item[] = {
 	CONF_CUSTOM("server-https", _config_server_https, NULL),
 	CONF_CUSTOM("nameserver", _config_nameserver, NULL),
 	CONF_CUSTOM("address", _config_address, NULL),
+	CONF_CUSTOM("proxy-socks5", _config_proxy_socks5, NULL),
+	CONF_CUSTOM("proxy-http", _config_proxy_http, NULL),
 	CONF_YESNO("ipset-timeout", &dns_conf_ipset_timeout_enable),
 	CONF_CUSTOM("ipset", _config_ipset, NULL),
 	CONF_YESNO("nftset-timeout", &dns_conf_nftset_timeout_enable),
@@ -2635,6 +2882,7 @@ void dns_server_load_exit(void)
 	_config_ptr_table_destroy();
 	_config_host_table_destroy();
 	_config_qtype_soa_table_destroy();
+	_config_proxy_table_destroy();
 }
 
 static int _dns_conf_speed_check_mode_verify(void)

+ 40 - 2
src/dns_conf.h

@@ -1,6 +1,6 @@
 /*************************************************************************
  *
- * Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
  *
  * smartdns is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,6 +27,7 @@
 #include "hash.h"
 #include "hashtable.h"
 #include "list.h"
+#include "proxy.h"
 #include "radix.h"
 
 #ifdef __cpluscplus
@@ -41,8 +42,13 @@ extern "C" {
 #define DNS_MAX_NFTSET_FAMILYLEN 8
 #define DNS_MAX_NFTSET_NAMELEN 256
 #define DNS_GROUP_NAME_LEN 32
+
+#define PROXY_NAME_LEN 32
+#define PROXY_MAX_SERVERS 128
+
 #define DNS_NAX_GROUP_NUMBER 16
 #define DNS_MAX_IPLEN 64
+#define DNS_PROXY_MAX_LEN 128
 #define DNS_CONF_USRNAME_LEN 32
 #define DNS_MAX_SPKI_LEN 64
 #define DNS_MAX_URL_LEN 256
@@ -224,6 +230,17 @@ struct dns_hosts_table {
 extern struct dns_hosts_table dns_hosts_table;
 extern int dns_hosts_record_num;
 
+struct dns_proxy_names {
+	struct hlist_node node;
+	char proxy_name[PROXY_NAME_LEN];
+	struct list_head server_list;
+};
+
+struct dns_proxy_table {
+	DECLARE_HASHTABLE(proxy, 4);
+};
+extern struct dns_proxy_table dns_proxy_table;
+
 struct dns_servers {
 	char server[DNS_MAX_IPLEN];
 	unsigned short port;
@@ -238,6 +255,21 @@ struct dns_servers {
 	char httphost[DNS_MAX_CNAME_LEN];
 	char tls_host_verify[DNS_MAX_CNAME_LEN];
 	char path[DNS_MAX_URL_LEN];
+	char proxyname[PROXY_NAME_LEN];
+};
+
+struct dns_proxy_servers {
+	struct list_head list;
+	char server[DNS_MAX_IPLEN];
+	proxy_type_t type;
+	unsigned short port;
+	unsigned int server_flag;
+	char username[DNS_PROXY_MAX_LEN];
+	char password[DNS_PROXY_MAX_LEN];
+
+	int socks5;
+	int https;
+	int use_domain;
 };
 
 /* ip address lists of domain */
@@ -346,11 +378,15 @@ extern int dns_conf_serve_expired_reply_ttl;
 extern struct dns_servers dns_conf_servers[DNS_MAX_SERVERS];
 extern int dns_conf_server_num;
 
+/* proxy servers */
+extern struct dns_proxy_servers dns_conf_proxy_servers[PROXY_MAX_SERVERS];
+extern int dns_conf_proxy_server_num;
+
 extern int dns_conf_log_level;
 extern char dns_conf_log_file[DNS_MAX_PATH];
 extern size_t dns_conf_log_size;
 extern int dns_conf_log_num;
-extern int dns_conf_log_file_mode;;
+extern int dns_conf_log_file_mode;
 
 extern char dns_conf_ca_file[DNS_MAX_PATH];
 extern char dns_conf_ca_path[DNS_MAX_PATH];
@@ -415,6 +451,8 @@ int dns_server_load_conf(const char *file);
 
 int dns_server_check_update_hosts(void);
 
+struct dns_proxy_names *dns_server_get_proxy_nams(const char *proxyname);
+
 extern int config_addtional_file(void *data, int argc, char *argv[]);
 #ifdef __cpluscplus
 }

+ 2 - 2
src/dns_server.c

@@ -1,6 +1,6 @@
 /*************************************************************************
  *
- * Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
  *
  * smartdns is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -805,7 +805,7 @@ static int _dns_add_rrs(struct dns_server_post_context *context)
 	}
 
 	if (request->rcode != DNS_RC_NOERROR) {
-		tlog(TLOG_INFO, "result %s, qtype: %d, rtcode: %d", domain, context->qtype, request->rcode);
+		tlog(TLOG_INFO, "result: %s, qtype: %d, rtcode: %d", domain, context->qtype, request->rcode);
 	}
 
 	return ret;

+ 993 - 0
src/proxy.c

@@ -0,0 +1,993 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
+ *
+ * smartdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * smartdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 <arpa/inet.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <sys/epoll.h>
+
+#define PROXY_SOCKS5_VERSION 0x05
+#define PROXY_SOCKS5_NO_AUTH 0x00
+#define PROXY_SOCKS5_AUTH_USER_PASS 0x02
+#define PROXY_SOCKS5_AUTH_NONE 0xFF
+
+#define PROXY_SOCKS5_TYPE_IPV4 0x01
+#define PROXY_SOCKS5_TYPE_DOMAIN 0x03
+#define PROXY_SOCKS5_TYPE_IPV6 0x04
+
+#define PROXY_SOCKS5_CONNECT_TCP 0x01
+#define PROXY_SOCKS5_CONNECT_UDP 0x03
+
+#define PROXY_MAX_EVENTS 64
+#define PROXY_BUFFER_SIZE (1024 * 8)
+#define PROXY_MAX_HOSTNAME_LEN 256
+
+typedef enum PROXY_CONN_STATE {
+	PROXY_CONN_INIT = 0,
+	PROXY_CONN_INIT_ACK = 1,
+	PROXY_CONN_AUTH = 2,
+	PROXY_CONN_AUTH_ACK = 3,
+	PROXY_CONN_CONNECTING = 4,
+	PROXY_CONN_CONNECTED = 5,
+} PROXY_CONN_STATE;
+
+struct proxy_conn {
+	proxy_type_t type;
+	PROXY_CONN_STATE state;
+	char host[DNS_MAX_CNAME_LEN];
+	unsigned short port;
+	int fd;
+	int udp_fd;
+	int buffer_len;
+	int is_udp;
+	struct sockaddr_storage udp_dest_addr;
+	socklen_t udp_dest_addrlen;
+	struct proxy_server_info *server_info;
+};
+
+/* upstream server groups */
+struct proxy_server_info {
+	struct hlist_node node;
+	char proxy_name[PROXY_NAME_LEN];
+	struct sockaddr_storage server_addr;
+	socklen_t server_addrlen;
+	struct proxy_info info;
+};
+
+struct proxy_struct {
+	int run;
+	int epoll_fd;
+	pthread_t tid;
+	pthread_mutex_t proxy_lock;
+	DECLARE_HASHTABLE(proxy_server, 4);
+};
+
+static struct proxy_struct proxy;
+
+const char *proxy_socks5_status_code[] = {
+	"success",
+	"general SOCKS server failure",
+	"connection not allowed by ruleset",
+	"Network unreachable",
+	"Host unreachable",
+	"Connection refused",
+	"TTL expired",
+	"Command not supported",
+	"Address type not supported",
+};
+
+/* get server group by name */
+static struct proxy_server_info *_proxy_get_server_info(const char *proxy_name)
+{
+	unsigned long key;
+	struct proxy_server_info *server_info = NULL;
+	struct hlist_node *tmp = NULL;
+
+	if (proxy_name == NULL) {
+		return NULL;
+	}
+
+	key = hash_string(proxy_name);
+	hash_for_each_possible_safe(proxy.proxy_server, server_info, tmp, node, key)
+	{
+		if (strncmp(server_info->proxy_name, proxy_name, DNS_GROUP_NAME_LEN) != 0) {
+			continue;
+		}
+
+		return server_info;
+	}
+
+	return NULL;
+}
+
+static struct addrinfo *_proxy_getaddr(const char *host, int port, int type, int protocol)
+{
+	struct addrinfo hints;
+	struct addrinfo *result = NULL;
+	int ret = 0;
+	char port_str[32];
+
+	snprintf(port_str, sizeof(port_str), "%d", port);
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = type;
+	hints.ai_protocol = protocol;
+
+	ret = getaddrinfo(host, port_str, &hints, &result);
+	if (ret != 0) {
+		tlog(TLOG_ERROR, "get addr info failed. %s\n", gai_strerror(ret));
+		tlog(TLOG_ERROR, "host = %s, port = %d, type = %d, protocol = %d", host, port, type, protocol);
+		goto errout;
+	}
+
+	return result;
+errout:
+	if (result) {
+		freeaddrinfo(result);
+	}
+	return NULL;
+}
+
+int proxy_add(const char *proxy_name, struct proxy_info *info)
+{
+	unsigned long key;
+	char ip_str[PROXY_MAX_IPLEN];
+	int port = 0;
+	struct addrinfo *gai = NULL;
+	struct proxy_server_info *server_info = _proxy_get_server_info(proxy_name);
+
+	if (server_info) {
+		return -1;
+	}
+
+	server_info = malloc(sizeof(*server_info));
+	if (server_info == NULL) {
+		goto errout;
+	}
+
+	memset(server_info, 0, sizeof(*server_info));
+	memcpy(&server_info->info, info, sizeof(struct proxy_info));
+
+	if (parse_ip(info->server, ip_str, &port) != 0) {
+		goto errout;
+	}
+
+	port = info->port;
+	gai = _proxy_getaddr(info->server, port, SOCK_STREAM, 0);
+	if (gai == NULL) {
+		goto errout;
+	}
+
+	server_info->server_addrlen = gai->ai_addrlen;
+	memcpy(&server_info->server_addr, gai->ai_addr, gai->ai_addrlen);
+
+	safe_strncpy(server_info->proxy_name, proxy_name, PROXY_NAME_LEN);
+	key = hash_string(server_info->proxy_name);
+	hash_add(proxy.proxy_server, &server_info->node, key);
+
+	freeaddrinfo(gai);
+	return 0;
+errout:
+	if (server_info) {
+		free(server_info);
+		server_info = NULL;
+	}
+
+	if (gai) {
+		freeaddrinfo(gai);
+	}
+	return -1;
+}
+
+static int _proxy_remove(struct proxy_server_info *server_info)
+{
+	hash_del(&server_info->node);
+	free(server_info);
+
+	return 0;
+}
+
+int proxy_remove(const char *proxy_name)
+{
+	struct proxy_server_info *server_info = _proxy_get_server_info(proxy_name);
+	if (server_info == NULL) {
+		return 0;
+	}
+
+	_proxy_remove(server_info);
+
+	return 0;
+}
+
+static void _proxy_remove_all(void)
+{
+	struct proxy_server_info *server_info;
+	struct hlist_node *tmp = NULL;
+	unsigned int i = 0;
+
+	hash_for_each_safe(proxy.proxy_server, i, tmp, server_info, node)
+	{
+		_proxy_remove(server_info);
+	}
+}
+
+struct proxy_conn *proxy_conn_new(const char *proxy_name, const char *host, int port, int is_udp)
+{
+	struct proxy_conn *proxy_conn = NULL;
+	struct proxy_server_info *server_info = NULL;
+	struct addrinfo *gai = NULL;
+	int fd = -1;
+
+	server_info = _proxy_get_server_info(proxy_name);
+	if (server_info == NULL) {
+		goto errout;
+	}
+
+	if (is_udp == 1 && server_info->info.type != PROXY_SOCKS5) {
+		tlog(TLOG_WARN, "only socks5 support udp");
+		goto errout;
+	}
+
+	fd = socket(server_info->server_addr.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0);
+	if (fd < 0) {
+		goto errout;
+	}
+
+	proxy_conn = malloc(sizeof(*proxy_conn));
+	if (proxy_conn == NULL) {
+		goto errout;
+	}
+
+	memset(proxy_conn, 0, sizeof(*proxy_conn));
+	safe_strncpy(proxy_conn->host, host, DNS_MAX_CNAME_LEN);
+	proxy_conn->port = port;
+	proxy_conn->type = server_info->info.type;
+	proxy_conn->state = PROXY_CONN_INIT;
+	proxy_conn->server_info = server_info;
+	proxy_conn->fd = fd;
+	proxy_conn->udp_fd = -1;
+	proxy_conn->is_udp = is_udp;
+
+	return proxy_conn;
+errout:
+	if (proxy_conn) {
+		free(proxy_conn);
+		proxy_conn = NULL;
+	}
+
+	if (fd >= 0) {
+		close(fd);
+	}
+
+	if (gai) {
+		freeaddrinfo(gai);
+	}
+	return NULL;
+}
+
+void proxy_conn_free(struct proxy_conn *proxy_conn)
+{
+	if (proxy_conn == NULL) {
+		return;
+	}
+
+	if (proxy_conn->fd >= 0) {
+		close(proxy_conn->fd);
+	}
+
+	if (proxy_conn->udp_fd >= 0) {
+		close(proxy_conn->udp_fd);
+	}
+
+	free(proxy_conn);
+}
+
+int proxy_conn_connect(struct proxy_conn *proxy_conn)
+{
+	if (proxy_conn == NULL) {
+		return -1;
+	}
+
+	return connect(proxy_conn->fd, (struct sockaddr *)&proxy_conn->server_info->server_addr,
+				   proxy_conn->server_info->server_addrlen);
+}
+
+static int _proxy_handshake_socks5_create_udp_fd(struct proxy_conn *proxy_conn)
+{
+	int ret = 0;
+	char *gai_host = NULL;
+	int udp_fd = -1;
+	struct addrinfo *gai = NULL;
+
+	switch (proxy_conn->udp_dest_addr.ss_family) {
+	case AF_INET:
+		gai_host = "0.0.0.0";
+		break;
+	case AF_INET6:
+		gai_host = "::";
+		break;
+	default:
+		goto errout;
+		break;
+	}
+
+	gai = _proxy_getaddr(gai_host, 0, SOCK_DGRAM, 0);
+	udp_fd = socket(gai->ai_family, gai->ai_socktype | SOCK_CLOEXEC, 0);
+	if (udp_fd < 0) {
+		goto errout;
+	}
+
+	ret = bind(udp_fd, gai->ai_addr, gai->ai_addrlen);
+	if (ret < 0) {
+		goto errout;
+	}
+
+	freeaddrinfo(gai);
+	return udp_fd;
+errout:
+	if (gai) {
+		freeaddrinfo(gai);
+	}
+
+	return -1;
+}
+
+static int _proxy_handshake_socks5_connect_udp(struct proxy_conn *proxy_conn)
+{
+	int udp_fd = -1;
+
+	if (proxy_conn->is_udp == 0) {
+		return 0;
+	}
+
+	if (proxy_conn->udp_fd < 0) {
+		udp_fd = _proxy_handshake_socks5_create_udp_fd(proxy_conn);
+		if (udp_fd < 0) {
+			return -1;
+		}
+
+		proxy_conn->udp_fd = udp_fd;
+	}
+
+	return connect(proxy_conn->udp_fd, (struct sockaddr *)&proxy_conn->udp_dest_addr, proxy_conn->udp_dest_addrlen);
+}
+
+static proxy_handshake_state _proxy_handshake_socks5_reply_connect_addr(struct proxy_conn *proxy_conn)
+{
+	char buff[DNS_MAX_CNAME_LEN * 2];
+	int len = 0;
+	memset(buff, 0, sizeof(buff));
+	struct sockaddr_storage addr;
+	char *ptr = buff;
+	socklen_t addr_len = sizeof(addr);
+
+	buff[0] = PROXY_SOCKS5_VERSION;
+	if (proxy_conn->is_udp) {
+		buff[1] = PROXY_SOCKS5_CONNECT_UDP;
+	} else {
+		buff[1] = PROXY_SOCKS5_CONNECT_TCP;
+	}
+
+	buff[2] = 0x0;
+	ptr = buff + 3;
+	if (proxy_conn->server_info->info.use_domain) {
+		*ptr = PROXY_SOCKS5_TYPE_DOMAIN;
+		ptr++;
+
+		int domainlen = strnlen(proxy_conn->host, DNS_MAX_CNAME_LEN);
+		*ptr = domainlen;
+		ptr++;
+		memcpy(ptr, proxy_conn->host, domainlen);
+		ptr += domainlen;
+	} else {
+		if (proxy_conn->is_udp) {
+			memset(&addr, 0, proxy_conn->server_info->server_addrlen);
+			addr_len = proxy_conn->server_info->server_addrlen;
+			addr.ss_family = proxy_conn->server_info->server_addr.ss_family;
+		} else {
+			getaddr_by_host(proxy_conn->host, (struct sockaddr *)&addr, &addr_len);
+		}
+
+		switch (addr.ss_family) {
+		case AF_INET: {
+			struct sockaddr_in *addr_in = NULL;
+			addr_in = (struct sockaddr_in *)&addr;
+			*ptr = PROXY_SOCKS5_TYPE_IPV4;
+			ptr++;
+			memcpy(ptr, &addr_in->sin_addr.s_addr, 4);
+			ptr += 4;
+		} break;
+		case AF_INET6: {
+			struct sockaddr_in6 *addr_in6 = NULL;
+			addr_in6 = (struct sockaddr_in6 *)&addr;
+			if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {
+				*ptr = PROXY_SOCKS5_TYPE_IPV4;
+				ptr++;
+				memcpy(ptr, addr_in6->sin6_addr.s6_addr + 12, 4);
+				ptr += 4;
+			} else {
+				*ptr = PROXY_SOCKS5_TYPE_IPV6;
+				ptr++;
+				memcpy(ptr, addr_in6->sin6_addr.s6_addr, 16);
+				ptr += 16;
+			}
+		} break;
+		default:
+			return PROXY_HANDSHAKE_ERR;
+		}
+	}
+	*((short *)(ptr)) = htons(proxy_conn->port);
+	ptr += 2;
+
+	len = send(proxy_conn->fd, buff, ptr - buff, MSG_NOSIGNAL);
+	if (len != ptr - buff) {
+		tlog(TLOG_ERROR, "Send proxy request failed.");
+		return PROXY_HANDSHAKE_ERR;
+	}
+	proxy_conn->state = PROXY_CONN_CONNECTING;
+	return PROXY_HANDSHAKE_WANT_READ;
+}
+
+static proxy_handshake_state _proxy_handshake_socks5_send_auth(struct proxy_conn *proxy_conn)
+{
+	char buff[DNS_MAX_CNAME_LEN * 2];
+	int len = 0;
+	int offset = 0;
+	memset(buff, 0, sizeof(buff));
+
+	buff[0] = 0x1;
+	buff[1] = strnlen(proxy_conn->server_info->info.username, PROXY_MAX_NAMELEN);
+	safe_strncpy(buff + 2, proxy_conn->server_info->info.username, buff[1] + 1);
+	offset = buff[1] + 2;
+	buff[offset] = strnlen(proxy_conn->server_info->info.password, PROXY_MAX_NAMELEN);
+	safe_strncpy(buff + offset + 1, proxy_conn->server_info->info.password, buff[offset] + 1);
+	offset += buff[offset] + 1;
+	len = send(proxy_conn->fd, buff, offset, MSG_NOSIGNAL);
+	if (len != offset) {
+		tlog(TLOG_ERROR, "send auth failed, len = %d, errno = %s", len, strerror(errno));
+		return PROXY_HANDSHAKE_ERR;
+	}
+
+	proxy_conn->state = PROXY_CONN_AUTH_ACK;
+	return PROXY_HANDSHAKE_WANT_READ;
+}
+
+static proxy_handshake_state _proxy_handshake_socks5(struct proxy_conn *proxy_conn)
+{
+	int len = 0;
+	char buff[DNS_MAX_CNAME_LEN * 2];
+	memset(buff, 0, sizeof(buff));
+
+	switch (proxy_conn->state) {
+	case PROXY_CONN_INIT: {
+		buff[0] = PROXY_SOCKS5_VERSION;
+		buff[1] = 0x2; // 2 auth methods
+		buff[2] = PROXY_SOCKS5_NO_AUTH;
+		buff[3] = PROXY_SOCKS5_AUTH_USER_PASS;
+		len = send(proxy_conn->fd, buff, 4, MSG_NOSIGNAL);
+		if (len != 4) {
+			tlog(TLOG_ERROR, "init socks5 failed, errno = %s", strerror(errno));
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		proxy_conn->state = PROXY_CONN_INIT_ACK;
+		return PROXY_HANDSHAKE_WANT_READ;
+	} break;
+	case PROXY_CONN_INIT_ACK:
+		len = recv(proxy_conn->fd, buff, sizeof(buff), 0);
+		if (len <= 0) {
+			if (errno == EAGAIN || errno == EWOULDBLOCK) {
+				return PROXY_HANDSHAKE_WANT_READ;
+			}
+
+			tlog(TLOG_ERROR, "recv socks5 init ack failed, errno = %s", strerror(errno));
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if (len != 2) {
+			tlog(TLOG_ERROR, "recv socks5 init ack failed, len = %d", len);
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if (buff[0] != PROXY_SOCKS5_VERSION) {
+			tlog(TLOG_ERROR, "Server not support socks5");
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if ((unsigned char)buff[1] == PROXY_SOCKS5_AUTH_NONE) {
+			tlog(TLOG_ERROR, "Server not support auth methods");
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		tlog(TLOG_INFO, "Server select auth method is %d", buff[1]);
+		if (buff[1] == PROXY_SOCKS5_AUTH_USER_PASS) {
+			return _proxy_handshake_socks5_send_auth(proxy_conn);
+		}
+
+		if (buff[1] == PROXY_SOCKS5_NO_AUTH) {
+			return _proxy_handshake_socks5_reply_connect_addr(proxy_conn);
+		}
+
+		tlog(TLOG_ERROR, "Server select invalid auth method %d", buff[1]);
+		return PROXY_HANDSHAKE_ERR;
+		break;
+	case PROXY_CONN_AUTH_ACK:
+		len = recv(proxy_conn->fd, buff, sizeof(buff), 0);
+		if (len <= 0) {
+			if (errno == EAGAIN || errno == EWOULDBLOCK) {
+				return PROXY_HANDSHAKE_WANT_READ;
+			}
+
+			tlog(TLOG_ERROR, "recv socks5 auth ack failed, errno = %s", strerror(errno));
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if (len != 2) {
+			tlog(TLOG_ERROR, "recv socks5 auth ack failed, len = %d", len);
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if (buff[0] != 0x1) {
+			tlog(TLOG_ERROR, "Server not support socks5");
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if (buff[1] != 0x0) {
+			tlog(TLOG_ERROR, "Server auth failed, code = %d", buff[1]);
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		tlog(TLOG_INFO, "Server auth success");
+		proxy_conn->state = PROXY_CONN_CONNECTING;
+		return _proxy_handshake_socks5_reply_connect_addr(proxy_conn);
+	case PROXY_CONN_CONNECTING: {
+		unsigned char addr[16];
+		unsigned short port = 0;
+		int use_dest_ip = 0;
+
+		int addr_len = 0;
+		len = recv(proxy_conn->fd, buff, sizeof(buff), 0);
+		if (len <= 0) {
+			if (errno == EAGAIN || errno == EWOULDBLOCK) {
+				return PROXY_HANDSHAKE_WANT_READ;
+			}
+
+			tlog(TLOG_ERROR, "recv socks5 connect ack failed, errno = %s", strerror(errno));
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if (len < 10) {
+			tlog(TLOG_ERROR, "Server reply connect addr failed, len = %d", len);
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if (buff[0] != PROXY_SOCKS5_VERSION) {
+			tlog(TLOG_ERROR, "Server not support socks5");
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if (buff[1] != 0) {
+			if ((unsigned char)buff[1] <= (sizeof(proxy_socks5_status_code) / sizeof(proxy_socks5_status_code[0]))) {
+				tlog(TLOG_ERROR, "Server replay failed, error code %s", proxy_socks5_status_code[(int)buff[1]]);
+			} else {
+				tlog(TLOG_ERROR, "Server replay failed, error code %x", buff[1]);
+			}
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		switch (buff[3]) {
+		case PROXY_SOCKS5_TYPE_IPV4: {
+			struct sockaddr_in *addr_in = NULL;
+			addr_in = (struct sockaddr_in *)&proxy_conn->udp_dest_addr;
+			proxy_conn->udp_dest_addrlen = sizeof(struct sockaddr_in);
+			if (len != 10) {
+				return PROXY_HANDSHAKE_ERR;
+			}
+
+			addr_len = 4;
+			memcpy(addr, buff + 4, addr_len);
+			port = ntohs(*((short *)(buff + 4 + addr_len)));
+			addr_in->sin_family = AF_INET;
+			addr_in->sin_addr.s_addr = *((int *)addr);
+			addr_in->sin_port = *((short *)(buff + 4 + addr_len));
+			if (addr[0] == 0 && addr[1] == 0 && addr[2] == 0 && addr[3] == 0) {
+				use_dest_ip = 1;
+			}
+
+			tlog(TLOG_DEBUG, "proxy dest: %d.%d.%d.%d:%d\n", addr[0], addr[1], addr[2], addr[3], port);
+		} break;
+		case PROXY_SOCKS5_TYPE_IPV6: {
+			struct sockaddr_in6 *addr_in6 = NULL;
+			addr_in6 = (struct sockaddr_in6 *)&proxy_conn->udp_dest_addr;
+			proxy_conn->udp_dest_addrlen = sizeof(struct sockaddr_in6);
+			if (len != 22) {
+				return PROXY_HANDSHAKE_ERR;
+			}
+
+			addr_len = 16;
+			memcpy(addr, buff + 4, addr_len);
+			port = ntohs(*((short *)(buff + 4 + addr_len)));
+			addr_in6->sin6_family = AF_INET6;
+			memcpy(addr_in6->sin6_addr.s6_addr, addr, addr_len);
+			addr_in6->sin6_port = *((short *)(buff + 4 + addr_len));
+
+			if (addr[0] == 0 && addr[1] == 0 && addr[2] == 0 && addr[3] == 0 && addr[4] == 0 && addr[5] == 0 &&
+				addr[6] == 0 && addr[7] == 0 && addr[8] == 0 && addr[9] == 0 && addr[10] == 0 && addr[11] == 0 &&
+				addr[12] == 0 && addr[13] == 0 && addr[14] == 0 && addr[15] == 0) {
+				use_dest_ip = 1;
+			}
+
+			tlog(TLOG_DEBUG, "proxy dest: [%x:%x:%x:%x:%x:%x:%x:%x]:%d\n", ntohs(*((short *)addr)),
+				 ntohs(*((short *)(addr + 2))), ntohs(*((short *)(addr + 4))), ntohs(*((short *)(addr + 6))),
+				 ntohs(*((short *)(addr + 8))), ntohs(*((short *)(addr + 10))), ntohs(*((short *)(addr + 12))),
+				 ntohs(*((short *)(addr + 14))), port);
+		} break;
+		default:
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		if (use_dest_ip && proxy_conn->is_udp) {
+			memcpy(&proxy_conn->udp_dest_addr, &proxy_conn->server_info->server_addr,
+				   proxy_conn->server_info->server_addrlen);
+			proxy_conn->udp_dest_addrlen = proxy_conn->server_info->server_addrlen;
+			switch (proxy_conn->udp_dest_addr.ss_family) {
+			case AF_INET: {
+				struct sockaddr_in *addr_in = NULL;
+				addr_in = (struct sockaddr_in *)&proxy_conn->udp_dest_addr;
+				addr_in->sin_port = *((short *)(buff + 4 + addr_len));
+			} break;
+			case AF_INET6: {
+				struct sockaddr_in6 *addr_in6 = NULL;
+				addr_in6 = (struct sockaddr_in6 *)&proxy_conn->udp_dest_addr;
+				addr_in6->sin6_port = *((short *)(buff + 4 + addr_len));
+			} break;
+			default:
+				return PROXY_HANDSHAKE_ERR;
+				break;
+			}
+		}
+
+		if (_proxy_handshake_socks5_connect_udp(proxy_conn) != 0) {
+			return PROXY_HANDSHAKE_ERR;
+		}
+
+		proxy_conn->state = PROXY_CONN_CONNECTED;
+		tlog(TLOG_INFO, "connected to socks proxy server.");
+		return PROXY_HANDSHAKE_CONNECTED;
+	} break;
+	default:
+		tlog(TLOG_ERROR, "client socks status %d is invalid", proxy_conn->state);
+		return PROXY_HANDSHAKE_ERR;
+	}
+
+	return PROXY_HANDSHAKE_ERR;
+}
+
+static int _proxy_handshake_http(struct proxy_conn *proxy_conn)
+{
+	int len = 0;
+	proxy_handshake_state ret = PROXY_HANDSHAKE_ERR;
+	char buff[4096];
+	struct http_head *http_head = NULL;
+
+	switch (proxy_conn->state) {
+	case PROXY_CONN_INIT: {
+		char connecthost[DNS_MAX_CNAME_LEN * 2];
+		struct sockaddr_storage addr;
+
+		socklen_t addr_len = sizeof(addr);
+		getaddr_by_host(proxy_conn->host, (struct sockaddr *)&addr, &addr_len);
+
+		if (proxy_conn->server_info->info.use_domain) {
+			snprintf(connecthost, sizeof(connecthost), "%s:%d", proxy_conn->host, proxy_conn->port);
+		} else {
+			struct sockaddr_in *addr_in;
+			addr_in = (struct sockaddr_in *)&addr;
+			unsigned char *paddr = (unsigned char *)&addr_in->sin_addr.s_addr;
+			snprintf(connecthost, sizeof(connecthost), "%d.%d.%d.%d:%d", paddr[0], paddr[1], paddr[2], paddr[3],
+					 proxy_conn->port);
+		}
+
+		int msglen = 0;
+
+		if (proxy_conn->server_info->info.username[0] == '\0') {
+			msglen = snprintf(buff, sizeof(buff),
+							  "CONNECT %s HTTP/1.1\r\n"
+							  "Host: %s\r\n"
+							  "Proxy-Connection: Keep-Alive\r\n\r\n",
+							  connecthost, connecthost);
+		} else {
+			char auth[256];
+			char base64_auth[256 * 2];
+			snprintf(auth, sizeof(auth), "%s:%s", proxy_conn->server_info->info.username,
+					 proxy_conn->server_info->info.password);
+			SSL_base64_encode(auth, strlen(auth), base64_auth);
+
+			msglen = snprintf(buff, sizeof(buff),
+							  "CONNECT %s HTTP/1.1\r\n"
+							  "Host: %s\r\n"
+							  "Proxy-Authorization: Basic %s\r\n"
+							  "Proxy-Connection: Keep-Alive\r\n\r\n",
+							  connecthost, connecthost, base64_auth);
+		}
+
+		len = send(proxy_conn->fd, buff, msglen, MSG_NOSIGNAL);
+		if (len != msglen) {
+			tlog(TLOG_ERROR, "init https failed, len = %d, errno = %s", len, strerror(errno));
+			goto out;
+		}
+
+		proxy_conn->state = PROXY_CONN_CONNECTING;
+		ret = PROXY_HANDSHAKE_WANT_READ;
+		goto out;
+	} break;
+	case PROXY_CONN_CONNECTING: {
+		http_head = http_head_init(4096);
+		if (http_head == NULL) {
+			goto out;
+		}
+
+		len = recv(proxy_conn->fd, buff, sizeof(buff), 0);
+		if (len <= 0) {
+			if (len == 0) {
+				tlog(TLOG_ERROR, "remote server closed.");
+			} else {
+				tlog(TLOG_ERROR, "recv failed, errno = %s", strerror(errno));
+			}
+			goto out;
+		}
+
+		len = http_head_parse(http_head, buff, len);
+		if (len < 0) {
+			if (len == -1) {
+				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 failed, server return http code : %d, %s",
+				 http_head_get_httpcode(http_head), http_head_get_httpcode_msg(http_head));
+			goto out;
+		}
+
+		proxy_conn->state = PROXY_CONN_CONNECTED;
+		ret = PROXY_HANDSHAKE_CONNECTED;
+		goto out;
+	} break;
+	default:
+		goto out;
+		break;
+	}
+
+out:
+	if (http_head) {
+		http_head_destroy(http_head);
+	}
+
+	return ret;
+}
+
+proxy_handshake_state proxy_conn_handshake(struct proxy_conn *proxy_conn)
+{
+	if (proxy_conn == NULL) {
+		return -1;
+	}
+
+	if (proxy_conn->state == PROXY_CONN_CONNECTED) {
+		return PROXY_HANDSHAKE_OK;
+	}
+
+	switch (proxy_conn->type) {
+	case PROXY_SOCKS5:
+		return _proxy_handshake_socks5(proxy_conn);
+	case PROXY_HTTP:
+		return _proxy_handshake_http(proxy_conn);
+	default:
+		return PROXY_HANDSHAKE_ERR;
+	}
+
+	return PROXY_HANDSHAKE_ERR;
+}
+
+static int _proxy_is_tcp_connected(struct proxy_conn *proxy_conn)
+{
+	char buff[1];
+	int ret = 0;
+	ret = recv(proxy_conn->fd, buff, 1, MSG_PEEK | MSG_DONTWAIT);
+	if (ret < 0) {
+		if (errno == EAGAIN || errno == EWOULDBLOCK) {
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int proxy_conn_sendto(struct proxy_conn *proxy_conn, const void *buf, size_t len, int flags,
+					  const struct sockaddr *dest_addr, socklen_t addrlen)
+{
+	char buffer[PROXY_BUFFER_SIZE];
+	int buffer_len = 0;
+	int ret = 0;
+
+	if (proxy_conn == NULL) {
+		return -1;
+	}
+
+	if (_proxy_is_tcp_connected(proxy_conn) == 0) {
+		errno = ECONNRESET;
+		return -1;
+	}
+
+	buffer[0] = 0x00;
+	buffer[1] = 0x00;
+	buffer[2] = 0x00;
+	buffer_len += 3;
+
+	switch (dest_addr->sa_family) {
+	case AF_INET:
+		buffer[3] = PROXY_SOCKS5_TYPE_IPV4;
+		memcpy(buffer + 4, &((struct sockaddr_in *)dest_addr)->sin_addr.s_addr, 4);
+		memcpy(buffer + 8, &((struct sockaddr_in *)dest_addr)->sin_port, 2);
+		buffer_len += 7;
+		break;
+	case AF_INET6:
+		buffer[3] = PROXY_SOCKS5_TYPE_IPV6;
+		memcpy(buffer + 4, &((struct sockaddr_in6 *)dest_addr)->sin6_addr.s6_addr, 16);
+		memcpy(buffer + 20, &((struct sockaddr_in6 *)dest_addr)->sin6_port, 2);
+		buffer_len += 19;
+		break;
+	default:
+		return -1;
+	}
+
+	memcpy(buffer + buffer_len, buf, len);
+	buffer_len += len;
+
+	ret = sendto(proxy_conn->udp_fd, buffer, buffer_len, MSG_NOSIGNAL, (struct sockaddr *)&proxy_conn->udp_dest_addr,
+				 proxy_conn->udp_dest_addrlen);
+	if (ret != buffer_len) {
+		return -1;
+	}
+
+	return len;
+}
+
+int proxy_conn_recvfrom(struct proxy_conn *proxy_conn, void *buf, size_t len, int flags, struct sockaddr *src_addr,
+						socklen_t *addrlen)
+{
+	char buffer[PROXY_BUFFER_SIZE];
+	int buffer_len = 0;
+	int ret = 0;
+
+	if (proxy_conn == NULL) {
+		return -1;
+	}
+
+	ret = recvfrom(proxy_conn->udp_fd, buffer, sizeof(buffer), MSG_NOSIGNAL, NULL, 0);
+	if (ret <= 0) {
+		return -1;
+	}
+
+	if (buffer[0] != 0x00 || buffer[1] != 0x00 || buffer[2] != 0x00) {
+		return -1;
+	}
+
+	switch (buffer[3]) {
+	case PROXY_SOCKS5_TYPE_IPV4:
+		if (ret < 10) {
+			return -1;
+		}
+
+		if (src_addr) {
+			memset(src_addr, 0, sizeof(struct sockaddr_in));
+			((struct sockaddr_in *)src_addr)->sin_family = AF_INET;
+			memcpy(&((struct sockaddr_in *)src_addr)->sin_addr.s_addr, buffer + 4, 4);
+			memcpy(&((struct sockaddr_in *)src_addr)->sin_port, buffer + 8, 2);
+		}
+
+		if (addrlen) {
+			*addrlen = sizeof(struct sockaddr_in);
+		}
+
+		buffer_len = 10;
+		break;
+	case PROXY_SOCKS5_TYPE_IPV6:
+		if (ret < 22) {
+			return -1;
+		}
+
+		if (src_addr) {
+			memset(src_addr, 0, sizeof(struct sockaddr_in6));
+			((struct sockaddr_in6 *)src_addr)->sin6_family = AF_INET6;
+			memcpy(&((struct sockaddr_in6 *)src_addr)->sin6_addr.s6_addr, buffer + 4, 16);
+			memcpy(&((struct sockaddr_in6 *)src_addr)->sin6_port, buffer + 20, 2);
+		}
+
+		if (addrlen) {
+			*addrlen = sizeof(struct sockaddr_in6);
+		}
+
+		buffer_len = 22;
+		break;
+	default:
+
+		return -1;
+	}
+
+	if (ret - buffer_len > (int)len) {
+		return -1;
+	}
+
+	memcpy(buf, buffer + buffer_len, ret - buffer_len);
+	return ret - buffer_len;
+}
+
+int proxy_conn_get_fd(struct proxy_conn *proxy_conn)
+{
+	if (proxy_conn == NULL) {
+		return -1;
+	}
+
+	return proxy_conn->fd;
+}
+
+int proxy_conn_get_udpfd(struct proxy_conn *proxy_conn)
+{
+	if (proxy_conn == NULL) {
+		return -1;
+	}
+
+	return proxy_conn->udp_fd;
+}
+
+int proxy_conn_is_udp(struct proxy_conn *proxy_conn)
+{
+	if (proxy_conn == NULL) {
+		return -1;
+	}
+
+	return proxy_conn->is_udp;
+}
+
+int proxy_init()
+{
+	memset(&proxy, 0, sizeof(proxy));
+	hash_init(proxy.proxy_server);
+	return 0;
+}
+
+int proxy_exit()
+{
+	_proxy_remove_all();
+	return 0;
+}

+ 88 - 0
src/proxy.h

@@ -0,0 +1,88 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
+ *
+ * smartdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * smartdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SMART_DNS_PROXY_H
+#define SMART_DNS_PROXY_H
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#define PROXY_MAX_IPLEN 256
+#define PROXY_MAX_NAMELEN 128
+
+#ifdef __cplusplus
+extern "C" {
+#endif /*__cplusplus */
+
+typedef enum {
+	PROXY_SOCKS5,
+	PROXY_HTTP,
+	PROXY_TYPE_END,
+} proxy_type_t;
+
+typedef enum {
+	PROXY_HANDSHAKE_ERR = -1,
+	PROXY_HANDSHAKE_OK = 0,
+	PROXY_HANDSHAKE_CONNECTED = 1,
+	PROXY_HANDSHAKE_WANT_READ = 2,
+	PROXY_HANDSHAKE_WANT_WRITE = 3,
+} proxy_handshake_state;
+
+struct proxy_info {
+	proxy_type_t type;
+	char server[PROXY_MAX_IPLEN];
+	unsigned short port;
+	int use_domain;
+	char username[PROXY_MAX_NAMELEN];
+	char password[PROXY_MAX_NAMELEN];
+};
+
+struct proxy_conn;
+
+int proxy_init(void);
+
+int proxy_exit(void);
+
+int proxy_add(const char *proxy_name, struct proxy_info *info);
+
+int proxy_remove(const char *proxy_name);
+
+struct proxy_conn *proxy_conn_new(const char *proxy_name, const char *host, int port, int is_udp);
+
+int proxy_conn_get_fd(struct proxy_conn *proxy_conn);
+
+int proxy_conn_get_udpfd(struct proxy_conn *proxy_conn);
+
+int proxy_conn_is_udp(struct proxy_conn *proxy_conn);
+
+void proxy_conn_free(struct proxy_conn *proxy_conn);
+
+int proxy_conn_connect(struct proxy_conn *proxy_conn);
+
+int proxy_conn_sendto(struct proxy_conn *proxy_conn, const void *buf, size_t len, int flags,
+					  const struct sockaddr *dest_addr, socklen_t addrlen);
+
+int proxy_conn_recvfrom(struct proxy_conn *proxy_conn, void *buf, size_t len, int flags, struct sockaddr *src_addr,
+						socklen_t *addrlen);
+
+proxy_handshake_state proxy_conn_handshake(struct proxy_conn *proxy_conn);
+
+#ifdef __cplusplus
+}
+#endif /*__cplusplus */
+#endif

+ 42 - 1
src/smartdns.c

@@ -1,6 +1,6 @@
 /*************************************************************************
  *
- * Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
  *
  * smartdns is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -270,6 +270,7 @@ static int _smartdns_add_servers(void)
 		flags.server_flag = dns_conf_servers[i].server_flag;
 		flags.result_flag = dns_conf_servers[i].result_flag;
 		flags.set_mark = dns_conf_servers[i].set_mark;
+		safe_strncpy(flags.proxyname, dns_conf_servers[i].proxyname, sizeof(flags.proxyname));
 		ret = dns_client_add_server(dns_conf_servers[i].server, dns_conf_servers[i].port, dns_conf_servers[i].type,
 									&flags);
 		if (ret != 0) {
@@ -302,6 +303,33 @@ static int _smartdns_add_servers(void)
 	return 0;
 }
 
+static int _proxy_add_servers(void)
+{
+	unsigned long i = 0;
+	struct hlist_node *tmp = NULL;
+	struct dns_proxy_names *proxy = NULL;
+	struct dns_proxy_servers *server = NULL;
+	struct dns_proxy_servers *server_tmp = NULL;
+
+	hash_for_each_safe(dns_proxy_table.proxy, i, tmp, proxy, node)
+	{
+		list_for_each_entry_safe(server, server_tmp, &proxy->server_list, list)
+		{
+			struct proxy_info info;
+			memset(&info, 0, sizeof(info));
+			info.type = server->type;
+			info.port = server->port;
+			safe_strncpy(info.server, server->server, PROXY_MAX_IPLEN);
+			safe_strncpy(info.username, server->username, PROXY_MAX_NAMELEN);
+			safe_strncpy(info.password, server->password, PROXY_MAX_NAMELEN);
+			info.use_domain = server->use_domain;
+			proxy_add(proxy->proxy_name, &info);
+		}
+	}
+
+	return 0;
+}
+
 static int _smartdns_set_ecs_ip(void)
 {
 	int ret = 0;
@@ -386,6 +414,17 @@ static int _smartdns_init(void)
 		goto errout;
 	}
 
+	ret = proxy_init();
+	if (ret != 0) {
+		tlog(TLOG_ERROR, "start proxy failed.\n");
+		goto errout;
+	}
+
+	ret = _proxy_add_servers();
+	if (ret != 0) {
+		tlog(TLOG_ERROR, "add proxy servers failed.");
+	}
+
 	ret = dns_server_init();
 	if (ret != 0) {
 		tlog(TLOG_ERROR, "start dns server failed.\n");
@@ -397,6 +436,7 @@ static int _smartdns_init(void)
 		tlog(TLOG_ERROR, "start dns client failed.\n");
 		goto errout;
 	}
+
 	ret = _smartdns_add_servers();
 	if (ret != 0) {
 		tlog(TLOG_ERROR, "add servers failed.");
@@ -423,6 +463,7 @@ static void _smartdns_exit(void)
 {
 	tlog(TLOG_INFO, "smartdns exit...");
 	dns_client_exit();
+	proxy_exit();
 	fast_ping_exit();
 	dns_server_exit();
 	_smartdns_destroy_ssl();

+ 20 - 2
src/util.c

@@ -1,6 +1,6 @@
 /*************************************************************************
  *
- * Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
  *
  * smartdns is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -684,6 +684,24 @@ errout:
 	return -1;
 }
 
+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;
@@ -1469,7 +1487,7 @@ int dns_packet_debug(const char *packet_file)
 	char buff[DNS_PACKSIZE];
 
 	tlog_set_maxlog_count(0);
-	tlog_setlogscreen(1);;
+	tlog_setlogscreen(1);
 	tlog_setlevel(TLOG_DEBUG);
 
 	info = _dns_read_packet_file(packet_file);

+ 3 - 1
src/util.h

@@ -1,6 +1,6 @@
 /*************************************************************************
  *
- * Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
  *
  * smartdns is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -89,6 +89,8 @@ unsigned char *SSL_SHA256(const unsigned char *d, size_t n, unsigned char *md);
 
 int SSL_base64_decode(const char *in, unsigned char *out);
 
+int SSL_base64_encode(const void *in, int in_len, char *out);
+
 int create_pid_file(const char *pid_file);
 
 /* Parse a TLS packet for the Server Name Indication extension in the client