浏览代码

Support DNS Over HTTPS

Nick Peng 6 年之前
父节点
当前提交
2d01ed8a04

+ 3 - 2
ReadMe.md

@@ -94,7 +94,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, TLS queries, and non-53 port queries, effectively avoiding DNS pollution.
+   Support UDP, TCP, TLS, HTTPS queries, and non-53 port queries, effectively avoiding DNS pollution.
 
 4. **Domain IP address specification**  
    Support configuring IP address of specific domain to achieve the effect of advertising filtering, and avoid malicious websites.
@@ -549,6 +549,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
 |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>`[-check-edns]`: edns filter. <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| server 8.8.8.8:53 -blacklist-ip -check-edns
 |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>`[-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| 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>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <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| 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>`[-blacklist-ip]`: The "-blacklist-ip" parameter is to filtering IPs which is configured by "blacklist-ip". <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| server-https https://cloudflare-dns.com/dns-query
 |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
 |nameserver|To query domain with specific server group|None|nameserver /domain/[group\|-], `group` is the group name, `-` means ignore this rule, use the `-group` parameter in the related server|nameserver /www.example.com/office
 |ipset|Domain IPSet|None|ipset /domain/[ipset\|-], `-` for ignore|ipset /www.example.com/pass
@@ -570,7 +571,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
     * Domain name matching supports ignoring specific domain names, and can be individually matched to IPv4, IPV6, and supports diversified customization.
     * Enhance the ad blocking feature, return SOA record, this block ads better;
     * IPV4, IPV6 dual stack IP optimization mechanism, in the case of dual network, choose the fastest network.
-    * Supports the latest TLS protocol and provides secure DNS query capabilities.
+    * Supports the latest TLS, HTTPS protocol and provides secure DNS query capabilities.
     * DNS anti-poison mechanism, and a variety of mechanisms to avoid DNS pollution.
     * ECS support, the query results are better and more accurate.
     * IP blacklist support, ignoring the blacklist IP to make domain name queries better and more accurate.

+ 3 - 2
ReadMe_zh-CN.md

@@ -94,7 +94,7 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
    支持从域名所属IP地址列表中查找到访问速度最快的IP地址,并返回给客户端,避免DNS污染,提高网络访问速度。
 
 1. **支持多种查询协议**  
-   支持UDP,TCP,TLS查询,以及非53端口查询,有效避免DNS污染。
+   支持UDP,TCP,TLS, HTTPS查询,以及非53端口查询,有效避免DNS污染。
 
 1. **特定域名IP地址指定**  
    支持指定域名的IP地址,达到广告过滤效果,避免恶意网站的效果。
@@ -552,6 +552,7 @@ https://github.com/pymumu/smartdns/releases
 |server|上游UDP DNS|无|可重复<br>`[ip][:port]`:服务器IP,端口可选。<br>`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。<br>`[-check-edns]`:edns过滤。<br>`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。<br>`[-exclude-default-group]`:将DNS服务器从默认组中排除| server 8.8.8.8:53 -blacklist-ip -check-edns -group g1
 |server-tcp|上游TCP DNS|无|可重复<br>`[ip][:port]`:服务器IP,端口可选。<br>`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。<br>`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。<br>`[-exclude-default-group]`:将DNS服务器从默认组中排除| 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>`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。<br>`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。<br>`[-exclude-default-group]`:将DNS服务器从默认组中排除| 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>`[-blacklist-ip]`:blacklist-ip参数指定使用blacklist-ip配置IP过滤结果。<br>`[-group [group] ...]`:DNS服务器所属组,比如office, foreign,和nameserver配套使用。<br>`[-exclude-default-group]`:将DNS服务器从默认组中排除| server-https https://cloudflare-dns.com/dns-query
 |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
 |nameserver|指定域名使用server组解析|无|nameserver /domain/[group\|-], `group`为组名,`-`表示忽略此规则,配套server中的`-group`参数使用| nameserver /www.example.com/office
 |ipset|域名IPSET|None|ipset /domain/[ipset\|-], `-`表示忽略|ipset /www.example.com/pass
@@ -573,7 +574,7 @@ https://github.com/pymumu/smartdns/releases
     * 域名匹配支持忽略特定域名,可单独匹配IPv4, IPV6,支持多样化定制。
     * 针对广告屏蔽功能做增强,返回SOA,屏蔽广告效果更佳;
     * IPV4,IPV6双栈IP优选机制,在双网情况下,选择最快的网络通讯。
-    * 支持最新的TLS协议,提供安全的DNS查询能力。
+    * 支持最新的TLS, HTTPS协议,提供安全的DNS查询能力。
     * DNS防抢答机制,及多种机制避免DNS污染。
     * ECS支持,是查询结果更佳准确。
     * IP黑名单,忽略IP机制,使域名查询更佳准确。

二进制
doc/architecture.png


二进制
doc/architecture.vsdx


+ 6 - 0
etc/smartdns/smartdns.conf

@@ -102,6 +102,12 @@ log-level info
 # server-tls 8.8.8.8
 # server-tls 1.0.0.1
 
+# remote https dns server list
+# server-https https://[host]:[port]/path [-blacklist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]
+#   -spki-pin: TLS spki pin to verify.
+# default port is 443
+# server-https https://cloudflare-dns.com/dns-query
+
 # specific nameserver to domain
 # nameserver /domain/[group|-]
 # nameserver /www.example.com/office, Set the domain name to use the appropriate server group.

+ 5 - 1
package/luci/files/luci/model/cbi/smartdns/smartdns.lua

@@ -142,13 +142,16 @@ s:option(Value, "name", translate("DNS Server Name"), translate("DNS Server Name
 
 ---- IP address
 o = s:option(Value, "ip", translate("ip"), translate("DNS Server ip"))
-o.datatype = "ipaddr"
+o.datatype = "or(ipaddr, string)"
 o.rmempty = false 
 ---- port
 o = s:option(Value, "port", translate("port"), translate("DNS Server port"))
 o.placeholder = "default"
 o.datatype    = "port"
 o.rempty      = true
+o:depends("type", "udp")
+o:depends("type", "tcp")
+o:depends("type", "tls")
 
 ---- type
 o = s:option(ListValue, "type", translate("type"), translate("DNS Server type"))
@@ -156,6 +159,7 @@ o.placeholder = "udp"
 o:value("udp", translate("udp"))
 o:value("tcp", translate("tcp"))
 o:value("tls", translate("tls"))
+o:value("https", translate("https"))
 o.default     = "udp"
 o.rempty      = false
 

+ 9 - 2
package/luci/files/luci/model/cbi/smartdns/upstream.lua

@@ -18,13 +18,16 @@ s:option(Value, "name", translate("DNS Server Name"), translate("DNS Server Name
 
 ---- IP address
 o = s:option(Value, "ip", translate("ip"), translate("DNS Server ip"))
-o.datatype = "ipaddr"
+o.datatype = "or(host, string)"
 o.rmempty = false 
 ---- port
 o = s:option(Value, "port", translate("port"), translate("DNS Server port"))
 o.placeholder = "default"
 o.datatype    = "port"
 o.rempty      = true
+o:depends("type", "udp")
+o:depends("type", "tcp")
+o:depends("type", "tls")
 
 ---- type
 o = s:option(ListValue, "type", translate("type"), translate("DNS Server type"))
@@ -32,6 +35,7 @@ o.placeholder = "udp"
 o:value("udp", translate("udp"))
 o:value("tcp", translate("tcp"))
 o:value("tls", translate("tls"))
+o:value("https", translate("https"))
 o.default     = "udp"
 o.rempty      = false
 
@@ -54,6 +58,7 @@ end
 o = s:option(Flag, "check_edns", translate("Anti Answer Forgery"), translate("Anti answer forgery, if DNS does not work properly after enabling, please turn off this feature"))
 o.rmempty     = false
 o.default     = o.disabled
+o:depends("type", "udp")
 o.cfgvalue    = function(...)
     return Flag.cfgvalue(...) or "0"
 end
@@ -61,8 +66,10 @@ end
 ---- SPKI pin
 o = s:option(Value, "spki_pin", translate("TLS SPKI Pinning"), translate("Used to verify the validity of the TLS server, The value is Base64 encoded SPKI fingerprint, leaving blank to indicate that the validity of TLS is not verified."))
 o.default     = ""
-o.datatype    = "wpakey"
+o.datatype    = "string"
 o.rempty      = true
+o:depends("type", "tls")
+o:depends("type", "https")
 
 ---- other args
 o = s:option(Value, "addition_arg", translate("Additional Server Args"), translate("Additional Args for upstream dns servers"))

+ 13 - 0
package/openwrt/control/postinst

@@ -5,6 +5,19 @@ chmod +x /etc/init.d/smartdns
 mkdir -p /var/etc/smartdns/
 
 [ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0
+
+if [ ! -f "/var/etc/smartdns/address.conf" ]; then
+    cp /etc/smartdns/address.conf /var/etc/smartdns/address.conf
+fi
+
+if [ ! -f "/var/etc/smartdns/blacklist-ip.conf" ]; then
+    cp /etc/smartdns/blacklist-ip.conf /var/etc/smartdns/blacklist-ip.conf
+fi
+
+if [ ! -f "/var/etc/smartdns/custom.conf" ]; then
+    cp /etc/smartdns/custom.conf /var/etc/smartdns/custom.conf
+fi
+
 . ${IPKG_INSTROOT}/lib/functions.sh
 default_postinst $0 $@
 ret=$?

+ 7 - 1
package/openwrt/files/etc/init.d/smartdns

@@ -155,9 +155,11 @@ load_server()
 		SERVER="server-tcp"
 	elif [ "$type" = "tls" ]; then
 		SERVER="server-tls"
+	elif [ "$type" = "https" ]; then
+		SERVER="server-https"
 	fi
 
-	if [ ! -z "`echo $ip | grep ":"`" ]; then
+	if [ ! -z "`echo $ip | grep ":" | grep -v "https://"`" ]; then
 		if [ -z "`echo $ip | grep "\["`" ]; then
 			ip="[$ip]"
 		fi
@@ -185,6 +187,10 @@ load_server()
 		DNS_ADDRESS="$ip"
 	fi
 
+	if [ "$type" = "https" ]; then
+		DNS_ADDRESS="$ip"
+	fi
+
 	conf_append "$SERVER" "$DNS_ADDRESS $ADDITIONAL_ARGS $addition_arg"
 }
 

+ 1 - 1
src/Makefile

@@ -1,7 +1,7 @@
 
 BIN=smartdns 
 OBJS_LIB=lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o lib/conf.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 $(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 $(OBJS_LIB)
 CFLAGS +=-O2 -g -Wall -Wstrict-prototypes -fno-omit-frame-pointer -Wstrict-aliasing 
 CFLAGS +=-Iinclude
 CFLAGS += -DBASE_FILE_NAME=\"$(notdir $<)\"

+ 223 - 69
src/dns_client.c

@@ -22,6 +22,7 @@
 #include "dns_conf.h"
 #include "fast_ping.h"
 #include "hashtable.h"
+#include "http_parse.h"
 #include "list.h"
 #include "tlog.h"
 #include "util.h"
@@ -96,9 +97,6 @@ struct dns_server_info {
 	/* server type */
 	dns_server_type_t type;
 
-	unsigned char *spki;
-	int spki_len;
-
 	/* client socket */
 	int fd;
 	int ttl;
@@ -107,7 +105,6 @@ struct dns_server_info {
 	SSL_CTX *ssl_ctx;
 	SSL_SESSION *ssl_session;
 	dns_server_status status;
-	unsigned int result_flag;
 
 	struct dns_server_buff send_buff;
 	struct dns_server_buff recv_buff;
@@ -124,6 +121,8 @@ struct dns_server_info {
 		struct sockaddr_in6 in6;
 		struct sockaddr addr;
 	};
+
+	struct client_dns_server_flags flags;
 };
 
 /* upstream server group member */
@@ -298,8 +297,9 @@ static struct dns_server_info *_dns_client_get_server(char *server_ip, int port,
 	case DNS_SERVER_UDP:
 		sock_type = SOCK_DGRAM;
 		break;
-	case DNS_SERVER_TLS:
 	case DNS_SERVER_TCP:
+	case DNS_SERVER_TLS:
+	case DNS_SERVER_HTTPS:
 		sock_type = SOCK_STREAM;
 		break;
 	default:
@@ -572,26 +572,88 @@ static void _dns_client_group_remove_all(void)
 	}
 }
 
+int dns_client_spki_decode(const char *spki, unsigned char *spki_data_out)
+{
+	int spki_data_len = -1;
+
+	spki_data_len = SSL_base64_decode(spki, spki_data_out);
+
+	if (spki_data_len != SHA256_DIGEST_LENGTH) {
+		return -1;
+	}
+
+	return spki_data_len;
+}
+
+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_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_TLS: {
+		struct client_dns_server_flag_tls *flag_tls = &server_info->flags.tls;
+		spki = flag_tls->spki;
+		*spki_len = flag_tls->spi_len;
+	} break;
+		break;
+	case DNS_SERVER_TCP:
+		break;
+	default:
+		return NULL;
+		break;
+	}
+
+	if (*spki_len <= 0) {
+		return NULL;
+	}
+
+	return spki;
+}
+
 /* add dns server information */
-static int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_server_type_t server_type, unsigned int server_flag, unsigned int result_flag,
-								  int ttl, char *spki)
+static int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_server_type_t server_type, struct client_dns_server_flags *flags)
 {
 	struct dns_server_info *server_info = NULL;
 	unsigned char *spki_data = NULL;
 	int spki_data_len = 0;
+	int ttl = 0;
 
-	/* read SPKI value, base64 sha256 value */
-	if (spki && (strlen(spki) < DNS_MAX_SPKI_LEN)) {
-		spki_data = malloc(DNS_MAX_SPKI_LEN);
-		if (spki_data) {
-			memset(spki_data, 0, DNS_MAX_SPKI_LEN);
-			spki_data_len = SSL_base64_decode(spki, spki_data);
-			if (spki_data_len != SHA256_DIGEST_LENGTH) {
-				free(spki_data);
-				spki_data = NULL;
-				spki_data_len = 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;
+		}
+	} break;
+	case DNS_SERVER_HTTPS: {
+		struct client_dns_server_flag_https *flag_https = &flags->https;
+		spki_data_len = flag_https->spi_len;
+	} break;
+	case DNS_SERVER_TLS: {
+		struct client_dns_server_flag_tls *flag_tls = &flags->tls;
+		spki_data_len = flag_tls->spi_len;
+	} break;
+		break;
+	case DNS_SERVER_TCP:
+		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 */
@@ -605,7 +667,7 @@ static int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_ser
 	}
 
 	if (server_type != DNS_SERVER_UDP) {
-		result_flag &= (~DNSSERVER_FLAG_CHECK_TTL);
+		flags->result_flag &= (~DNSSERVER_FLAG_CHECK_TTL);
 	}
 
 	memset(server_info, 0, sizeof(*server_info));
@@ -615,14 +677,12 @@ static int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_ser
 	server_info->type = server_type;
 	server_info->fd = 0;
 	server_info->status = DNS_SERVER_STATUS_INIT;
-	server_info->result_flag = result_flag;
 	server_info->ttl = ttl;
 	server_info->ttl_range = 0;
-	server_info->spki = spki_data;
-	server_info->spki_len = spki_data_len;
+	memcpy(&server_info->flags, flags, sizeof(server_info->flags));
 
 	/* exclude this server from default group */
-	if ((server_flag & SERVER_FLAG_EXCLUDE_DEFAULT) == 0) {
+	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 to default group failed.");
 			goto errout;
@@ -630,7 +690,7 @@ static int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_ser
 	}
 
 	/* if server type is TLS, create ssl context */
-	if (server_type == DNS_SERVER_TLS) {
+	if (server_type == DNS_SERVER_TLS || server_type == DNS_SERVER_HTTPS) {
 		server_info->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
 		if (server_info->ssl_ctx == NULL) {
 			tlog(TLOG_ERROR, "init ssl failed.");
@@ -646,15 +706,17 @@ static int _dns_client_server_add(char *server_ip, struct addrinfo *gai, dns_ser
 	memcpy(&server_info->addr, gai->ai_addr, gai->ai_addrlen);
 
 	/* start ping task */
-	if (ttl <= 0 && (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 (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;
+			if (ttl < 0) {
+				server_info->ttl_range = -ttl;
+			}
 		}
 	}
 
@@ -741,10 +803,6 @@ static void _dns_client_server_remove_all(void)
 	{
 		list_del(&server_info->list);
 		_dns_client_server_close(server_info);
-		if (server_info->spki) {
-			free(server_info->spki);
-			server_info->spki = NULL;
-		}
 		free(server_info);
 	}
 	pthread_mutex_unlock(&client.server_list_lock);
@@ -779,8 +837,7 @@ static int _dns_client_server_remove(char *server_ip, struct addrinfo *gai, dns_
 	return -1;
 }
 
-static int _dns_client_server_operate(char *server_ip, int port, dns_server_type_t server_type, unsigned int server_flag, unsigned int result_flag, int ttl,
-									  char *spki, int operate)
+static int _dns_client_server_operate(char *server_ip, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags, int operate)
 {
 	char port_s[8];
 	int sock_type;
@@ -796,8 +853,9 @@ static int _dns_client_server_operate(char *server_ip, int port, dns_server_type
 	case DNS_SERVER_UDP:
 		sock_type = SOCK_DGRAM;
 		break;
-	case DNS_SERVER_TLS:
 	case DNS_SERVER_TCP:
+	case DNS_SERVER_TLS:
+	case DNS_SERVER_HTTPS:
 		sock_type = SOCK_STREAM;
 		break;
 	default:
@@ -815,7 +873,7 @@ static int _dns_client_server_operate(char *server_ip, int port, dns_server_type
 
 	if (operate == 0) {
 		/* add server */
-		ret = _dns_client_server_add(server_ip, gai, server_type, server_flag, result_flag, ttl, spki);
+		ret = _dns_client_server_add(server_ip, gai, server_type, flags);
 		if (ret != 0) {
 			goto errout;
 		}
@@ -835,14 +893,14 @@ errout:
 	return -1;
 }
 
-int dns_client_add_server(char *server_ip, int port, dns_server_type_t server_type, unsigned server_flag, unsigned int result_flag, int ttl, char *spki)
+int dns_client_add_server(char *server_ip, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags)
 {
-	return _dns_client_server_operate(server_ip, port, server_type, server_flag, result_flag, ttl, spki, 0);
+	return _dns_client_server_operate(server_ip, port, server_type, flags, 0);
 }
 
 int dns_client_remove_server(char *server_ip, int port, dns_server_type_t server_type)
 {
-	return _dns_client_server_operate(server_ip, port, server_type, 0, 0, 0, NULL, 1);
+	return _dns_client_server_operate(server_ip, port, server_type, NULL, 1);
 }
 
 int dns_server_num(void)
@@ -1101,7 +1159,7 @@ static int _dns_client_recv(struct dns_server_info *server_info, unsigned char *
 
 	/* get query reference */
 	query = _dns_client_get_request(packet->head.id, domain);
-	if (query == NULL || (query && has_opt == 0 && server_info->result_flag & DNSSERVER_FLAG_CHECK_EDNS)) {
+	if (query == NULL || (query && has_opt == 0 && server_info->flags.result_flag & DNSSERVER_FLAG_CHECK_EDNS)) {
 		if (query) {
 			_dns_client_query_release(query);
 		}
@@ -1123,7 +1181,7 @@ static int _dns_client_recv(struct dns_server_info *server_info, unsigned char *
 
 	/* notify caller dns query result */
 	if (query->callback) {
-		ret = query->callback(query->domain, DNS_QUERY_RESULT, server_info->result_flag, packet, inpacket, inpacket_len, query->user_ptr);
+		ret = query->callback(query->domain, DNS_QUERY_RESULT, server_info->flags.result_flag, packet, inpacket, inpacket_len, query->user_ptr);
 		if (request_num == 0 || ret) {
 			/* if all server replied, or done, stop query, release resource */
 			_dns_client_query_remove(query);
@@ -1326,7 +1384,7 @@ static int _dns_client_create_socket(struct dns_server_info *server_info)
 		return _dns_client_create_socket_udp(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) {
+	} else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS) {
 		return _DNS_client_create_socket_tls(server_info);
 	} else {
 		return -1;
@@ -1382,7 +1440,7 @@ static int _dns_client_process_udp(struct dns_server_info *server_info, struct e
 
 	tlog(TLOG_DEBUG, "recv udp packet from %s, len: %d, ttl: %d", gethost_by_addr(from_host, sizeof(from_host), (struct sockaddr *)&from), len, ttl);
 
-	if ((ttl != server_info->ttl) && (server_info->ttl > 0) && (server_info->result_flag & DNSSERVER_FLAG_CHECK_TTL)) {
+	if ((ttl != server_info->ttl) && (server_info->ttl > 0) && (server_info->flags.result_flag & DNSSERVER_FLAG_CHECK_TTL)) {
 		/* If TTL check is enabled but the TTL is inconsistent, it is considered to be a fake dns packet */
 		if ((ttl < server_info->ttl - server_info->ttl_range) || (ttl > server_info->ttl + server_info->ttl_range)) {
 			/* tlog(TLOG_DEBUG, "TTL mismatch, from:%d, local %d, discard result", ttl, server_info->ttl); */
@@ -1521,7 +1579,7 @@ static int _dns_client_socket_send(struct dns_server_info *server_info)
 		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) {
+	} else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS) {
 		return _dns_client_socket_ssl_send(server_info->ssl, server_info->send_buff.data, server_info->send_buff.len);
 	} else {
 		return -1;
@@ -1534,7 +1592,7 @@ static int _dns_client_socket_recv(struct dns_server_info *server_info)
 		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) {
+	} else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS) {
 		return _dns_client_socket_ssl_recv(server_info->ssl, server_info->recv_buff.data + server_info->recv_buff.len,
 										   DNS_TCP_BUFFER - server_info->recv_buff.len);
 	} else {
@@ -1545,7 +1603,9 @@ static int _dns_client_socket_recv(struct dns_server_info *server_info)
 static int _dns_client_process_tcp(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now)
 {
 	int len;
+	int dns_packet_len = 0;
 	int ret = -1;
+	struct http_head *http_head = NULL;
 	unsigned char *inpacket_data = NULL;
 
 	if (event->events & EPOLLIN) {
@@ -1591,29 +1651,61 @@ static int _dns_client_process_tcp(struct dns_server_info *server_info, struct e
 		}
 
 		while (1) {
-			/* 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 errout;
-			}
+			if (server_info->type == DNS_SERVER_HTTPS) {
+				http_head = http_head_init(4096);
+				if (http_head == NULL) {
+					goto errout;
+				}
 
-			if (len > server_info->recv_buff.len - 2) {
-				/* len is not expceded, wait and recv */
-				break;
-			}
+				len = http_head_parse(http_head, (char *)server_info->recv_buff.data, server_info->recv_buff.len);
+				if (len < 0) {
+					http_head_destroy(http_head);
+					if (len == -1) {
+						break;
+					}
+					goto errout;
+				}
 
-			inpacket_data = server_info->recv_buff.data + 2;
+				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 errout;
+				}
+
+				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 errout;
+				}
+
+				if (len > server_info->recv_buff.len - 2) {
+					/* len is not expceded, wait and recv */
+					break;
+				}
+
+				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);
 
 			/* process result */
-			if (_dns_client_recv(server_info, inpacket_data, len, &server_info->addr, server_info->ai_addrlen) != 0) {
+			if (_dns_client_recv(server_info, inpacket_data, dns_packet_len, &server_info->addr, server_info->ai_addrlen) != 0) {
 				goto errout;
 			}
-			len += 2;
+
+			if (http_head) {
+				http_head_destroy(http_head);
+				http_head = NULL;
+			}
+
 			server_info->recv_buff.len -= len;
 
 			/* move to next result */
@@ -1677,6 +1769,10 @@ static int _dns_client_process_tcp(struct dns_server_info *server_info, struct e
 	return 0;
 
 errout:
+	if (http_head) {
+		http_head_destroy(http_head);
+	}
+
 	pthread_mutex_lock(&client.server_list_lock);
 	server_info->recv_buff.len = 0;
 	server_info->send_buff.len = 0;
@@ -1705,6 +1801,8 @@ static int _dns_client_tls_verify(struct dns_server_info *server_info)
 	unsigned char *key_data = NULL;
 	unsigned char *key_data_tmp = NULL;
 	unsigned char *key_sha256 = NULL;
+	char *spki = NULL;
+	int spki_len = 0;
 
 	cert = SSL_get_peer_certificate(server_info->ssl);
 	if (cert == NULL) {
@@ -1751,9 +1849,10 @@ static int _dns_client_tls_verify(struct dns_server_info *server_info)
 	*ptr = 0;
 	tlog(TLOG_DEBUG, "cert SPKI pin(%s): %s", "sha256", cert_fingerprint);
 
-	if (server_info->spki) {
+	spki = _dns_client_server_get_spki(server_info, &spki_len);
+	if (spki) {
 		/* check SPKI */
-		if (memcmp(server_info->spki, key_sha256, server_info->spki_len) != 0) {
+		if (memcmp(spki, key_sha256, spki_len) != 0) {
 			tlog(TLOG_INFO, "server %s cert spki is invalid", server_info->ip);
 			goto errout;
 		} else {
@@ -1858,7 +1957,7 @@ static int _dns_client_process(struct dns_server_info *server_info, struct epoll
 	} 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) {
+	} else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS) {
 		/* recive from tls */
 		return _dns_client_process_tls(server_info, event, now);
 	} else {
@@ -1992,6 +2091,56 @@ static int _dns_client_send_tls(struct dns_server_info *server_info, void *packe
 	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"
+						"content-type: application/dns-message\r\n"
+						"Content-Length: %d\r\n"
+						"\r\n",
+						https_flag->path, https_flag->host, 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) {
+		return -1;
+	}
+
+	send_len = _dns_client_socket_ssl_send(server_info->ssl, inpacket, http_len);
+	if (send_len < 0) {
+		if (errno == EAGAIN || 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) {
+			SSL_shutdown(server_info->ssl);
+		}
+		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;
+}
+
 static int _dns_client_send_packet(struct dns_query_struct *query, void *packet, int len)
 {
 	struct dns_server_info *server_info = NULL;
@@ -2031,6 +2180,11 @@ static int _dns_client_send_packet(struct dns_query_struct *query, void *packet,
 			ret = _dns_client_send_tls(server_info, packet, len);
 			send_err = errno;
 			break;
+		case DNS_SERVER_HTTPS:
+			/* https query */
+			ret = _dns_client_send_https(server_info, packet, len);
+			send_err = errno;
+			break;
 		default:
 			/* unsupport query type */
 			ret = -1;

+ 33 - 1
src/dns_client.h

@@ -2,6 +2,7 @@
 #define _SMART_DNS_CLIENT_H
 
 #include "dns.h"
+#define DNS_SERVER_SPKI_LEN  64
 #define DNS_SERVER_GROUP_DEFAULT "default"
 
 typedef enum {
@@ -35,8 +36,39 @@ int dns_client_query(char *domain, int qtype, dns_client_callback callback, void
 
 void dns_client_exit(void);
 
+struct client_dns_server_flag_udp {
+	int ttl;
+};
+
+struct client_dns_server_flag_tls {
+	char spki[DNS_SERVER_SPKI_LEN];
+	int spi_len;
+	char host[DNS_MAX_CNAME_LEN];
+};
+
+struct client_dns_server_flag_https {
+	char spki[DNS_SERVER_SPKI_LEN];
+	int spi_len;
+	char host[DNS_MAX_CNAME_LEN];
+	char path[DNS_MAX_CNAME_LEN];
+};
+
+struct client_dns_server_flags {
+	dns_server_type_t type;
+	unsigned int server_flag;
+	unsigned int result_flag;
+
+	union {
+		struct client_dns_server_flag_udp udp;
+		struct client_dns_server_flag_tls tls;
+		struct client_dns_server_flag_https https;
+	};
+};
+
+int dns_client_spki_decode(const char *spki, unsigned char *spki_data_out);
+
 /* add remote dns server */
-int dns_client_add_server(char *server_ip, int port, dns_server_type_t server_type, unsigned int server_flag, unsigned int result_flag, int ttl, char *spki);
+int dns_client_add_server(char *server_ip, int port, dns_server_type_t server_type, struct client_dns_server_flags *flags);
 
 /* remove remote dns server */
 int dns_client_remove_server(char *server_ip, int port, dns_server_type_t server_type);

+ 27 - 3
src/dns_conf.c

@@ -68,6 +68,8 @@ int dns_conf_ipset_timeout_enable;
 struct dns_edns_client_subnet dns_conf_ipv4_ecs;
 struct dns_edns_client_subnet dns_conf_ipv6_ecs;
 
+char dns_conf_sni_proxy_ip[DNS_MAX_IPLEN];
+
 /* create and get dns server group */
 static struct dns_server_groups *_dns_conf_get_group(const char *group_name)
 {
@@ -186,11 +188,24 @@ static int _config_server(int argc, char *argv[], dns_server_type_t type, int de
 
 	server = &dns_conf_servers[index];
 	server->spki[0] = '\0';
+	server->path[0] = '\0';
+	server->hostname[0] = '\0';
+
 	ip = argv[1];
 
-	/* parse ip, port from ip */
-	if (parse_ip(ip, server->server, &port) != 0) {
-		return -1;
+	if (type == DNS_SERVER_HTTPS) {
+		if (parse_uri(ip, NULL, server->server, &port, server->path) != 0) {
+			return -1;
+		}
+		strncpy(server->hostname, server->server, sizeof(server->hostname));
+		if (server->path[0] == 0) {
+			strcpy(server->path, "/");
+		}
+	} else {
+		/* 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 */
@@ -706,6 +721,14 @@ 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_nameserver(void *data, int argc, char *argv[])
 {
 	struct dns_nameserver_rule *nameserver_rule = NULL;
@@ -954,6 +977,7 @@ static struct config_item _config_item[] = {
 	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("nameserver", _config_nameserver, NULL),
 	CONF_CUSTOM("address", _config_address, NULL),
 	CONF_YESNO("ipset-timeout", &dns_conf_ipset_timeout_enable),

+ 7 - 0
src/dns_conf.h

@@ -16,9 +16,11 @@
 #define DNS_NAX_GROUP_NUMBER 16
 #define DNS_MAX_IPLEN 64
 #define DNS_MAX_SPKI_LEN 64
+#define DNS_MAX_URL_LEN 256
 #define DNS_MAX_PATH 1024
 #define DEFAULT_DNS_PORT 53
 #define DEFAULT_DNS_TLS_PORT 853
+#define DEFAULT_DNS_HTTPS_PORT 443
 #define DNS_MAX_CONF_CNAME_LEN 128
 #define SMARTDNS_CONF_FILE "/etc/smartdns/smartdns.conf"
 #define SMARTDNS_LOG_FILE "/var/log/smartdns.log"
@@ -93,6 +95,8 @@ struct dns_servers {
 	int ttl;
 	dns_server_type_t type;
 	char spki[DNS_MAX_SPKI_LEN];
+	char hostname[DNS_MAX_CNAME_LEN];
+	char path[DNS_MAX_URL_LEN];
 };
 
 /* ip address lists of domain */
@@ -131,6 +135,7 @@ struct dns_conf_address_rule {
 
 extern char dns_conf_server_ip[DNS_MAX_IPLEN];
 extern char dns_conf_server_tcp_ip[DNS_MAX_IPLEN];
+
 extern int dns_conf_tcp_idle_time;
 extern int dns_conf_cachesize;
 extern int dns_conf_prefetch;
@@ -166,6 +171,8 @@ extern int dns_conf_ipset_timeout_enable;
 extern struct dns_edns_client_subnet dns_conf_ipv4_ecs;
 extern struct dns_edns_client_subnet dns_conf_ipv6_ecs;
 
+extern char dns_conf_sni_proxy_ip[DNS_MAX_IPLEN];
+
 void dns_server_load_exit(void);
 
 int dns_server_load_conf(const char *file);

+ 113 - 1
src/dns_server.c

@@ -119,6 +119,9 @@ struct dns_request {
 		struct sockaddr addr;
 	};
 
+	dns_result_callback result_callback;
+	void *user_ptr;
+
 	int has_ping_result;
 	int has_ping_tcp;
 	int has_ptr;
@@ -522,6 +525,45 @@ static int _dns_setup_ipset(struct dns_request *request)
 	return ret;
 }
 
+static int _dns_result_callback(struct dns_request *request)
+{
+	char ip[DNS_MAX_CNAME_LEN];
+
+	if (request->result_callback == NULL) {
+		return 0;
+	}
+
+	ip[0] = 0;
+	if (request->qtype == DNS_T_A) {
+		if (request->has_ipv4 == 0) {
+			goto out;
+		}
+
+		sprintf(ip, "%d.%d.%d.%d", request->ipv4_addr[0], request->ipv4_addr[1], request->ipv4_addr[2], request->ipv4_addr[3]);
+
+		return request->result_callback(request->domain, request->rcode, request->qtype, ip, request->user_ptr);
+	} else if (request->qtype == DNS_T_AAAA) {
+		if (request->has_ipv6 == 0) {
+			goto out;
+		}
+
+		sprintf(ip, "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", 
+				 request->ipv6_addr[0], request->ipv6_addr[1], request->ipv6_addr[2], request->ipv6_addr[3], request->ipv6_addr[4], request->ipv6_addr[5],
+				 request->ipv6_addr[6], request->ipv6_addr[7], request->ipv6_addr[8], request->ipv6_addr[9], request->ipv6_addr[10], request->ipv6_addr[11],
+				 request->ipv6_addr[12], request->ipv6_addr[13], request->ipv6_addr[14], request->ipv6_addr[15]);
+
+		return request->result_callback(request->domain, request->rcode, request->qtype, ip, request->user_ptr);
+	}
+
+	request->result_callback(request->domain, DNS_RC_NXDOMAIN, request->qtype, ip, request->user_ptr);
+
+	return 0;
+out:
+
+	request->result_callback(request->domain, DNS_RC_NXDOMAIN, request->qtype, ip, request->user_ptr);
+	return 0;
+}
+
 static int _dns_server_request_complete(struct dns_request *request)
 {
 	char *cname = NULL;
@@ -610,6 +652,8 @@ static int _dns_server_request_complete(struct dns_request *request)
 	/* update ipset */
 	_dns_setup_ipset(request);
 
+	_dns_result_callback(request);
+
 	if (request->prefetch) {
 		return 0;
 	}
@@ -1514,7 +1558,13 @@ static int _dns_server_process_cache(struct dns_request *request, struct dns_pac
 	}
 
 	request->rcode = DNS_RC_NOERROR;
-	_dns_reply(request);
+
+	_dns_result_callback(request);
+
+	if (request->prefetch == 0) {
+		_dns_reply(request);
+	}
+
 	dns_cache_update(dns_cache);
 	dns_cache_release(dns_cache);
 
@@ -1781,6 +1831,68 @@ static int _dns_server_process_udp(struct dns_server_conn *dnsserver, struct epo
 	return _dns_server_recv(dnsserver, inpacket, len, &from, from_len);
 }
 
+int dns_server_query(char *domain, int qtype, dns_result_callback callback, void *user_ptr)
+{
+	int ret = -1;
+	struct dns_request *request = NULL;
+	const char *group_name = NULL;
+
+	request = malloc(sizeof(*request));
+	if (request == NULL) {
+		tlog(TLOG_ERROR, "malloc failed.\n");
+		goto errout;
+	}
+	memset(request, 0, sizeof(*request));
+	pthread_mutex_init(&request->ip_map_lock, NULL);
+	atomic_set(&request->adblock, 0);
+	request->ping_ttl_v4 = -1;
+	request->ping_ttl_v6 = -1;
+	request->prefetch = 1;
+	request->qtype = qtype;
+	request->rcode = DNS_RC_SERVFAIL;
+	request->result_callback = callback;
+	request->user_ptr = user_ptr;
+
+	request->id = 0;
+	hash_init(request->ip_map);
+	strncpy(request->domain, domain, DNS_MAX_CNAME_LEN);
+
+	/* lookup domain rule */
+	request->domain_rule = _dns_server_get_domain_rule(request->domain);
+
+	tlog(TLOG_INFO, "query domain %s, qtype = %d\n", request->domain, qtype);
+
+	/* process cache */
+	if (_dns_server_process_cache(request, NULL) == 0) {
+		ret = 0;
+		goto clean_exit;
+	}
+
+	_dns_server_request_get(request);
+	pthread_mutex_lock(&server.request_list_lock);
+	list_add_tail(&request->list, &server.request_list);
+	pthread_mutex_unlock(&server.request_list_lock);
+
+	_dns_server_request_get(request);
+	request->send_tick = get_tick_count();
+	request->request_wait++;
+
+	if (request->domain_rule) {
+		/* get nameserver rule */
+		if (request->domain_rule->rules[DOMAIN_RULE_NAMESERVER]) {
+			struct dns_nameserver_rule *nameserver_rule = request->domain_rule->rules[DOMAIN_RULE_NAMESERVER];
+			group_name = nameserver_rule->group_name;
+		}
+	}
+
+	/* send request */
+	ret = dns_client_query(request->domain, qtype, dns_server_resolve_callback, request, group_name);
+clean_exit:
+	return ret;
+errout:
+	return ret;	
+}
+
 static void _dns_server_client_touch(struct dns_server_conn *client)
 {
 	time(&client->last_request_time);

+ 8 - 0
src/dns_server.h

@@ -1,6 +1,8 @@
 #ifndef _SMART_DNS_SERVER_H
 #define _SMART_DNS_SERVER_H
 
+#include "dns.h"
+
 #ifdef __cpluscplus
 extern "C" {
 #endif
@@ -15,6 +17,12 @@ void dns_server_stop(void);
 
 void dns_server_exit(void);
 
+/* query result notify function */
+typedef int (*dns_result_callback)(char *domain, dns_rtcode_t rtcode, dns_type_t addr_type, char *ip, void *user_ptr);
+
+/* query domain */
+int dns_server_query(char *domain, int qtype, dns_result_callback callback, void *user_ptr);
+
 #ifdef __cpluscplus
 }
 #endif

+ 445 - 0
src/http_parse.c

@@ -0,0 +1,445 @@
+#include "http_parse.h"
+#include "hash.h"
+#include "hashtable.h"
+#include "jhash.h"
+#include "list.h"
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct http_head_fields {
+	struct hlist_node node;
+	struct list_head list;
+
+	char *name;
+	char *value;
+};
+
+struct http_head {
+	HTTP_HEAD_TYPE head_type;
+	HTTP_METHOD method;
+	char *url;
+	char *version;
+	int code;
+	char *code_msg;
+	int buff_size;
+	int buff_len;
+	char *buff;
+	int head_ok;
+	int head_len;
+	char *data;
+	int data_len;
+	int expect_data_len;
+	struct http_head_fields field_head;
+	DECLARE_HASHTABLE(field_map, 4);
+};
+
+/*
+ * Returns:
+ *  >=0  - success http data len
+ *  -1   - Incomplete request
+ *  -2   - parse failed
+ */
+struct http_head *http_head_init(int buffsize)
+{
+	struct http_head *http_head = NULL;
+	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);
+
+	buffer = malloc(buffsize);
+	if (buffer == NULL) {
+		goto errout;
+	}
+
+	http_head->buff = buffer;
+	http_head->buff_size = buffsize;
+
+	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)
+{
+	unsigned long key;
+	struct http_head_fields *filed;
+
+	key = hash_string(name);
+	hash_for_each_possible(http_head->field_map, filed, node, key)
+	{
+		if (strncmp(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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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_add_fields(struct http_head *http_head, char *name, char *value)
+{
+	unsigned long 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(name);
+	hash_add(http_head->field_map, &fields->node, key);
+
+	return 0;
+}
+
+static int _http_head_parse_response(struct http_head *http_head, char *key, char *value)
+{
+	char *field_start = NULL;
+	char *tmp_ptr = NULL;
+	char *result = 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 == ' ') {
+			*tmp_ptr = '\0';
+			if (ret_code == NULL) {
+				ret_code = field_start;
+			} else if (result == NULL) {
+				result = field_start;
+				break;
+			}
+
+			field_start = NULL;
+		}
+	}
+
+	if (field_start && result == NULL) {
+		result = field_start;
+	}
+
+	if (ret_code == NULL || result == NULL) {
+		return -1;
+	}
+
+	http_head->code = atol(ret_code);
+	http_head->code_msg = result;
+	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;
+
+	if (strncmp(key, "GET", sizeof("GET")) == 0) {
+		method = HTTP_METHOD_GET;
+	} else if (strncmp(key, "POST", sizeof("POST")) == 0) {
+		method = HTTP_METHOD_POST;
+	} else if (strncmp(key, "PUT", sizeof("PUT")) == 0) {
+		method = HTTP_METHOD_PUT;
+	} else if (strncmp(key, "DELETE", sizeof("DELETE")) == 0) {
+		method = HTTP_METHOD_DELETE;
+	} else if (strncmp(key, "TRACE", sizeof("TRACE")) == 0) {
+		method = HTTP_METHOD_TRACE;
+	} else if (strncmp(key, "CONNECT", sizeof("CONNECT")) == 0) {
+		method = HTTP_METHOD_CONNECT;
+	} else {
+		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;
+	}
+
+	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 = 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;
+}
+
+int http_head_parse(struct http_head *http_head, const char *data, int data_len)
+{
+	int i = 0;
+	char *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 (*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 (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;
+	}
+
+	http_head->buff_len += process_data_len;
+	if (http_head->data_len < http_head->expect_data_len) {
+		return -1;
+	}
+
+	return process_data_len;
+}
+
+void http_head_destroy(struct http_head *http_head)
+{
+	struct http_head_fields *fields, *tmp;
+
+	list_for_each_entry_safe(fields, tmp, &http_head->field_head.list, list)
+	{
+		list_del(&fields->list);
+		free(fields);
+	}
+
+	if (http_head->buff) {
+		free(http_head->buff);
+	}
+
+	free(http_head);
+}

+ 69 - 0
src/http_parse.h

@@ -0,0 +1,69 @@
+#ifndef HTTP_PARSER_H
+#define HTTP_PARSER_H
+
+#ifdef __cpluscplus
+extern "C" {
+#endif
+
+struct http_head;
+struct http_head_fields;
+
+typedef enum HTTP_METHOD {
+	HTTP_METHOD_INVALID = 0,
+	HTTP_METHOD_GET,
+	HTTP_METHOD_HEAD,
+	HTTP_METHOD_POST,
+	HTTP_METHOD_PUT,
+	HTTP_METHOD_DELETE,
+	HTTP_METHOD_TRACE,
+	HTTP_METHOD_CONNECT,
+} HTTP_METHOD;
+
+typedef enum HTTP_HEAD_TYPE {
+	HTTP_HEAD_INVALID = 0,
+	HTTP_HEAD_REQUEST = 1,
+	HTTP_HEAD_RESPONSE = 2,
+} HTTP_HEAD_TYPE;
+
+struct http_head *http_head_init(int buffsize);
+
+HTTP_HEAD_TYPE http_head_get_head_type(struct http_head *http_head);
+
+HTTP_METHOD http_head_get_method(struct http_head *http_head);
+
+const char *http_head_get_url(struct http_head *http_head);
+
+const char *http_head_get_httpversion(struct http_head *http_head);
+
+int http_head_get_httpcode(struct http_head *http_head);
+
+char *http_head_get_httpcode_msg(struct http_head *http_head);
+
+char *http_head_get_data(struct http_head *http_head);
+
+int http_head_get_data_len(struct http_head *http_head);
+
+struct http_head_fields *http_head_first_fields(struct http_head *http_head);
+
+struct http_head_fields *http_head_next_fields(struct http_head_fields *fields);
+
+const char *http_head_get_fields_value(struct http_head *http_head, const char *name);
+
+int http_head_lookup_fields(struct http_head_fields *fields, const char **name, const char **value);
+
+/*
+ * Returns:
+ *  >=0  - success http data len
+ *  -1   - Incomplete request
+ *  -2   - parse failed
+ *  -3   - buffer is small
+ */
+int http_head_parse(struct http_head *http_head, const char *data, int data_len);
+
+void http_head_destroy(struct http_head *http_head);
+
+#ifdef __cpluscplus
+}
+#endif
+
+#endif // !HTTP_PARSER_H

+ 31 - 3
src/smartdns.c

@@ -127,10 +127,38 @@ static int _smartdns_add_servers(void)
 	int ret = 0;
 	struct dns_server_groups *group = NULL;
 	struct dns_servers *server = NULL;
+	struct client_dns_server_flags flags;
 
 	for (i = 0; i < dns_conf_server_num; i++) {
-		ret = dns_client_add_server(dns_conf_servers[i].server, dns_conf_servers[i].port, dns_conf_servers[i].type, dns_conf_servers[i].server_flag,
-									dns_conf_servers[i].result_flag, dns_conf_servers[i].ttl, dns_conf_servers[i].spki);
+		memset(&flags, 0, sizeof(flags));
+		switch (dns_conf_servers[i].type) {
+		case DNS_SERVER_UDP: {
+			struct client_dns_server_flag_udp *flag_udp = &flags.udp;
+			flag_udp->ttl = dns_conf_servers[i].ttl;
+		} break;
+		case DNS_SERVER_HTTPS: {
+			struct client_dns_server_flag_https *flag_http = &flags.https;
+			flag_http->spi_len = dns_client_spki_decode(dns_conf_servers[i].spki, (unsigned char *)flag_http->spki);
+			strncpy(flag_http->host, dns_conf_servers[i].hostname, sizeof(flag_http->host));
+			strncpy(flag_http->path, dns_conf_servers[i].path, sizeof(flag_http->path));
+		} break;
+		case DNS_SERVER_TLS: {
+			struct client_dns_server_flag_tls *flag_tls = &flags.tls;
+			flag_tls->spi_len = dns_client_spki_decode(dns_conf_servers[i].spki, (unsigned char *)flag_tls->spki);
+			strncpy(flag_tls->host, dns_conf_servers[i].hostname, sizeof(flag_tls->host));
+		} break;
+			break;
+		case DNS_SERVER_TCP:
+			break;
+		default:
+			return -1;
+			break;
+		}
+
+		flags.type = dns_conf_servers[i].type;
+		flags.server_flag = dns_conf_servers[i].server_flag;
+		flags.result_flag = dns_conf_servers[i].result_flag;
+		ret = dns_client_add_server(dns_conf_servers[i].server, dns_conf_servers[i].port, dns_conf_servers[i].type, &flags);
 		if (ret != 0) {
 			tlog(TLOG_ERROR, "add server failed, %s:%d", dns_conf_servers[i].server, dns_conf_servers[i].port);
 			return -1;
@@ -297,7 +325,7 @@ static void _sig_error_exit(int signo, siginfo_t *siginfo, void *ct)
 #elif defined(__mips__)
 	PC = context->uc_mcontext.pc;
 #endif
-	tlog(TLOG_ERROR, "process exit with signal %d, code = %d, errno = %d, pid = %d, self = %d, pc = %#lx, addr = %#lx, build(%s %s)\n", signo, siginfo->si_code,
+	tlog(TLOG_FATAL, "process exit with signal %d, code = %d, errno = %d, pid = %d, self = %d, pc = %#lx, addr = %#lx, build(%s %s)\n", signo, siginfo->si_code,
 		 siginfo->si_errno, siginfo->si_pid, getpid(), PC, (unsigned long)siginfo->si_addr, __DATE__, __TIME__);
 
 	sleep(1);

+ 245 - 3
src/util.c

@@ -12,10 +12,11 @@
 #include <pthread.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
+#include <inttypes.h>
 
 #define TMP_BUFF_LEN_32 32
 
@@ -192,6 +193,53 @@ int parse_ip(const char *value, char *ip, int *port)
 	return 0;
 }
 
+int parse_uri(char *value, char *scheme, char *host, int *port, char *path)
+{
+	char *scheme_end = NULL;
+	int field_len = 0;
+	char *process_ptr = value;
+	char host_name[PATH_MAX];
+
+	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 + 1] = 0;
+		}
+		process_ptr += field_len + 3;
+	} else {
+		if (scheme) {
+			scheme[0] = '\0';
+		}
+	}
+
+	host_end = strstr(process_ptr, "/");
+	if (host_end == NULL) {
+		return parse_ip(process_ptr, host, port);
+	};
+
+	field_len = host_end - process_ptr;
+	if (field_len >= sizeof(host_name)) {
+		return -1;
+	}
+	memcpy(host_name, process_ptr, field_len);
+	host_name[field_len + 1] = 0;
+
+	if (parse_ip(host_name, 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;
@@ -457,7 +505,7 @@ static __attribute__((unused)) void _pthreads_locking_callback(int mode, int typ
 	}
 }
 
-static __attribute__((unused))  unsigned long _pthreads_thread_id(void)
+static __attribute__((unused)) unsigned long _pthreads_thread_id(void)
 {
 	unsigned long ret;
 
@@ -499,3 +547,197 @@ void SSL_CRYPTO_thread_cleanup(void)
 	OPENSSL_free(lock_cs);
 	OPENSSL_free(lock_count);
 }
+
+#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
+typedef struct Protocol {
+    const char *const name;
+    const uint16_t default_port;
+    int (*const parse_packet)(const char*, size_t, char *, const char **);
+    const char *const abort_message;
+    const size_t abort_message_len;
+} protocol_t;
+
+static int parse_extensions(const char *, size_t, char *, const char **);
+static int parse_server_name_extension(const char *, size_t, char *, const char **);
+
+const struct Protocol *const tls_protocol;
+
+static const protocol_t tls_protocol_st = {
+	.default_port = 443,
+	.parse_packet = &parse_tls_header,
+};
+const protocol_t *const tls_protocol = &tls_protocol_st;
+
+/* Parse a TLS packet for the Server Name Indication extension in the client
+ * hello handshake, returning the first servername 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;
+	char tls_version_major;
+	char tls_version_minor;
+	size_t pos = TLS_HEADER_LEN;
+	size_t len;
+
+	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;
+
+	/* 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 beinnging 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;
+
+	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;
+}

+ 17 - 0
src/util.h

@@ -16,6 +16,8 @@ int getaddr_by_host(char *host, struct sockaddr *addr, socklen_t *addr_len);
 
 int parse_ip(const char *value, char *ip, int *port);
 
+int parse_uri(char *value, char *scheme, char *host, int *port, char *path);
+
 int set_fd_nonblock(int fd, int nonblock);
 
 char *reverse_string(char *output, char *input, int len);
@@ -36,4 +38,19 @@ int SSL_base64_decode(const char *in, unsigned char *out);
 
 int create_pid_file(const char *pid_file);
 
+/* Parse a TLS packet for the Server Name Indication extension in the client
+ * hello handshake, returning the first servername 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);
+
 #endif