Support DNS Over HTTPS

This commit is contained in:
Nick Peng
2019-03-30 20:10:27 +08:00
parent 5501bfb6a3
commit 2d01ed8a04
21 changed files with 1264 additions and 88 deletions

View File

@@ -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;