Pārlūkot izejas kodu

Add TCP DNS server

Nick Peng 7 gadi atpakaļ
vecāks
revīzija
9eb8aa1571

+ 5 - 2
ReadMe.md

@@ -260,10 +260,10 @@ Download the matching version of the SmartDNS installation package. The correspo
         Log in to the router, click `Network`->`DHCP and DNS`, and modify `DNS forwardings` to:
 
         ```shell
-        /#/127.0.0.1#5353
+        /#/127.0.0.1#5053
         ```
 
-        Where `#5353` is the service port number of smartdns. If it is not modified, the default is 5353.
+        Where `#5053` is the service port number of smartdns. If it is not modified, the default is 5053.
 
     * **Check if the service is configured successfully**
 
@@ -371,7 +371,9 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
 |--|--|--|--|--|
 |server-name|DNS name|host name/smartdns|any string like hosname|server-name smartdns
 |bind|DNS bind port|[::]:53|IP:PORT|bind 192.168.1.1:53
+|bind-tcp|TCP mode DNS bind port|[::]:53|IP:PORT|bind-tcp 192.168.1.1:53
 |cache-size|Domain name result cache number|512|integer|cache-size 512
+|tcp-idle-time|TCP connection idle timeout|120|integer|tcp-idle-time 120
 |rr-ttl|Domain name TTL|Remote query result|number greater than 0|rr-ttl 600
 |rr-ttl-min|Domain name Minimum TTL|Remote query result|number greater than 0|rr-ttl-min 60
 |rr-ttl-max|Domain name Maximum TTL|Remote query result|number greater than 0|rr-ttl-max 600
@@ -382,6 +384,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use
 |conf-file|additional conf file|None|File path|conf-file /etc/smartdns/smartdns.more.conf
 |server|Upstream UDP DNS server|None|[ip][:port], Repeatable| server 8.8.8.8:53
 |server-tcp|Upstream TCP DNS server|None|[IP][:port], Repeatable| server-tcp 8.8.8.8:53
+|server-tls|Upstream TLS DNS server|None|[IP][:port], Repeatable| server-tls 8.8.8.8:853
 |address|Domain IP address|None|address /domain/ip| address /www.example.com/1.2.3.4
 |bogus-nxdomain|bogus IP address|None|[IP],Repeatable| bogus-nxdomain 1.2.3.4
 |force-AAAA-SOA|force AAAA query return SOA|no|[yes\|no]|force-AAAA-SOA yes

+ 5 - 2
ReadMe_zh-CN.md

@@ -260,10 +260,10 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
         登录路由器,点击`Network`->`DHCP and DNS`,修改`DNS forwardings(DNS转发)`为:
 
         ```shell
-        /#/127.0.0.1#5353
+        /#/127.0.0.1#5053
         ```
 
-        其中`#5353`为smartdns的服务端口号,未修改的情况下,默认为5353。
+        其中`#5053`为smartdns的服务端口号,未修改的情况下,默认为5053。
 
     * **检测上游服务是否配置成功**
 
@@ -371,7 +371,9 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
 |--|--|--|--|--|
 |server-name|DNS服务器名称|操作系统主机名/smartdns|符合主机名规格的字符串|server-name smartdns
 |bind|DNS监听端口号|[::]:53|IP:PORT|bind 192.168.1.1:53
+|bind-tcp|TCP模式DNS监听端口号|[::]:53|IP:PORT|bind-tcp 192.168.1.1:53
 |cache-size|域名结果缓存个数|512|数字|cache-size 512
+|tcp-idle-time|TCP链接空闲超时时间|120|数字|tcp-idle-time 120
 |rr-ttl|域名结果TTL|远程查询结果|大于0的数字|rr-ttl 600
 |rr-ttl-min|允许的最小TTL值|远程查询结果|大于0的数字|rr-ttl-min 60
 |rr-ttl-max|允许的最大TTL值|远程查询结果|大于0的数字|rr-ttl-max 600
@@ -382,6 +384,7 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
 |conf-file|附加配置文件|无|文件路径|conf-file /etc/smartdns/smartdns.more.conf
 |server|上游UDP DNS|无|[ip][:port],可重复| server 8.8.8.8:53
 |server-tcp|上游TCP DNS|无|[IP][:port],可重复| server-tcp 8.8.8.8:53
+|server-tls|上游TLS DNS|无|[IP][:port],可重复| server-tls 8.8.8.8:853
 |address|指定域名IP地址|无|address /domain/ip| address /www.example.com/1.2.3.4
 |bogus-nxdomain|假冒IP地址过滤|无|[ip],可重复| bogus-nxdomain 1.2.3.4
 |force-AAAA-SOA|强制AAAA地址返回SOA|no|[yes\|no]|force-AAAA-SOA yes

+ 6 - 0
package/luci/files/luci/i18n/smartdns.zh-cn.po

@@ -40,6 +40,12 @@ msgstr "IPV4 53端口重定向失败"
 msgid "IPV6 53 Port Redirect Failure"
 msgstr "IPV6 53端口重定向失败"
 
+msgid "TCP Server"
+msgstr "TCP服务器"
+
+msgid "Enable TCP DNS Server"
+msgstr "启用TCP服务器"
+
 msgid "IPV6 Server"
 msgstr "IPV6服务器"
 

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

@@ -31,11 +31,19 @@ o.rempty      = false
 
 ---- Port
 o = s:taboption("settings", Value, "port", translate("Local Port"), translate("Smartdns local server port"))
-o.placeholder = 5353
-o.default     = 5353
+o.placeholder = 5053
+o.default     = 5053
 o.datatype    = "port"
 o.rempty      = false
 
+---- Enable TCP server
+o = s:taboption("settings", Flag, "tcp_server", translate("TCP Server"), translate("Enable TCP DNS Server"))
+o.rmempty     = false
+o.default     = o.enabled
+o.cfgvalue    = function(...)
+    return Flag.cfgvalue(...) or "1"
+end
+
 ---- Support IPV6
 o = s:taboption("settings", Flag, "ipv6_server", translate("IPV6 Server"), translate("Enable IPV6 DNS Server"))
 o.rmempty     = false

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

@@ -100,13 +100,22 @@ start_service() {
 		conf_append "server-name" "$server_name"	
 	fi
 
-	config_get "port" "$section" "port" "5353"
+	config_get "port" "$section" "port" "5053"
 	config_get "ipv6_server" "$section" "ipv6_server" "1"
+	config_get "tcp_server" "$section" "tcp_server" "1"
 	if [ "$ipv6_server" = "1" ]; then
 		conf_append "bind" "[::]:$port"
 	else
 		conf_append "bind" ":$port"
 	fi
+
+	if [ "$tcp_server" = "1" ]; then
+		if [ "$ipv6_server" = "1" ]; then
+			conf_append "bind-tcp" "[::]:$port"
+		else
+			conf_append "bind-tcp" ":$port"
+		fi
+	fi
 	SMARTDNS_PORT="$port"
 
 	mkdir -p $(dirname $SMARTDNS_CONF)

+ 1 - 1
package/openwrt/make.sh

@@ -42,7 +42,7 @@ build()
 
     sed -i "s/^Architecture.*/Architecture: $ARCH/g" $ROOT/control/control
     sed -i "s/Version:.*/Version: $VER/" $ROOT/control/control
-    sed -i "s/^\(bind .*\):53/\1:5353/g" $ROOT/root/etc/smartdns/smartdns.conf
+    sed -i "s/^\(bind .*\):53/\1:5053/g" $ROOT/root/etc/smartdns/smartdns.conf
     if [ ! -z "$INST_SIZE" ]; then
         echo "Installed-Size: $INST_SIZE" >> $ROOT/control/control
     fi

+ 25 - 0
src/conf.c

@@ -14,6 +14,8 @@
 #define DEFAULT_DNS_CACHE_SIZE 512
 
 char dns_conf_server_ip[DNS_MAX_IPLEN];
+char dns_conf_server_tcp_ip[DNS_MAX_IPLEN];
+int dns_conf_tcp_idle_time = 120;
 int dns_conf_cachesize = DEFAULT_DNS_CACHE_SIZE;
 int dns_conf_prefetch = 0;
 struct dns_servers dns_conf_servers[DNS_MAX_SERVERS];
@@ -45,6 +47,14 @@ int config_bind(char *value)
 	return 0;
 }
 
+int config_bind_tcp(char *value)
+{
+	/* server bind address */
+	strncpy(dns_conf_server_tcp_ip, value, DNS_MAX_IPLEN);
+
+	return 0;
+}
+
 int config_server_name(char *value)
 {
 	strncpy(dns_conf_server_name, value, DNS_MAX_CONF_CNAME_LEN);
@@ -199,6 +209,19 @@ int config_server_tcp(char *value)
 	return config_server(value, DNS_SERVER_TCP, DEFAULT_DNS_PORT);
 }
 
+int config_tcp_idle_time(char *value)
+{
+	/* read dns cache size */
+	int idle_time = atoi(value);
+	if (idle_time < 0) {
+		return -1;
+	}
+
+	dns_conf_tcp_idle_time = idle_time;
+
+	return 0;
+}
+
 int config_server_tls(char *value)
 {
 	return config_server(value, DNS_SERVER_TLS, DEFAULT_DNS_TLS_PORT);
@@ -527,9 +550,11 @@ struct config_item {
 struct config_item config_item[] = {
 	{"server-name", config_server_name},
 	{"bind", config_bind},
+	{"bind-tcp", config_bind_tcp},
 	{"server", config_server_udp},
 	{"address", config_address},
 	{"server-tcp", config_server_tcp},
+	{"tcp-idle-time", config_tcp_idle_time},
 	{"server-tls", config_server_tls},
 	{"cache-size", config_cache_size},
 	{"prefetch-domain", config_cache_prefetch_domain},

+ 2 - 0
src/conf.h

@@ -49,6 +49,8 @@ struct dns_bogus_nxdomain {
 };
 
 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;
 extern struct dns_servers dns_conf_servers[DNS_MAX_SERVERS];

+ 0 - 2
src/dns.c

@@ -1290,8 +1290,6 @@ static int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsign
 		return -1;
 	}
 	
-
-	tlog(TLOG_DEBUG, "decode opt.");
 	while (context->ptr - start < rr_len) {
 		opt_code = dns_read_short(&context->ptr);
 		opt_len = dns_read_short(&context->ptr);

+ 505 - 33
src/dns_server.c

@@ -15,7 +15,7 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-
+#define _GNU_SOURCE
 #include "dns_server.h"
 #include "atomic.h"
 #include "conf.h"
@@ -50,18 +50,42 @@
 
 #define DNS_MAX_EVENTS 256
 #define DNS_SERVER_TMOUT_TTL (3 * 60)
+#define DNS_CONN_BUFF_SIZE 4096
+
+struct dns_conn_buf {
+	char buf[DNS_CONN_BUFF_SIZE];
+	int buffsize;
+	int size;
+};
+
+struct dns_server_conn {
+	struct list_head list;
+	atomic_t refcnt;
+	dns_server_type_t type;
+	int fd;
+	struct dns_conn_buf recvbuff;
+	struct dns_conn_buf sndbuff;
+
+	socklen_t addr_len;
+	struct sockaddr_storage addr;
+
+	time_t last_request_time;
+};
 
 /* dns server data */
 struct dns_server {
 	int run;
 	int epoll_fd;
-	int fd;
+	struct dns_server_conn udp_server;
+	struct dns_server_conn tcp_server;
 
 	/* dns request list */
 	pthread_mutex_t request_list_lock;
 	struct list_head request_list;
+	struct list_head client_list;
 };
 
+
 /* ip address lists of domain */
 struct dns_ip_address {
 	struct hlist_node node;
@@ -75,6 +99,8 @@ struct dns_ip_address {
 
 struct dns_request {
 	atomic_t refcnt;
+
+	struct dns_server_conn *client;
 	/* dns request list */
 	struct list_head list;
 
@@ -127,6 +153,7 @@ struct dns_request {
 	int prefetch;
 
 	pthread_mutex_t ip_map_lock;
+	
 	int ip_map_num;
 	DECLARE_HASHTABLE(ip_map, 4);
 };
@@ -242,14 +269,86 @@ static int _dns_add_rrs(struct dns_packet *packet, struct dns_request *request)
 	return ret;
 }
 
-static int _dns_reply_inpacket(struct dns_request *request, unsigned char *inpacket, int inpacket_len)
+static void _dns_server_client_release(struct dns_server_conn *client)
+{
+	int refcnt = atomic_dec_return(&client->refcnt);
+
+	if (refcnt) {
+		if (refcnt < 0) {
+			tlog(TLOG_ERROR, "BUG: refcnt is %d", refcnt);
+			abort();
+		}
+		return;
+	}
+
+	if (client->fd > 0) {
+		close(client->fd);
+		client->fd = -1;
+	}
+
+	list_del_init(&client->list);
+	free(client);
+}
+
+static void _dns_server_client_get(struct dns_server_conn *client)
+{
+	atomic_inc(&client->refcnt);
+}
+
+static int _dns_server_reply_tcp_to_buffer(struct dns_server_conn *client, void *packet, int len)
+{
+	struct epoll_event event;
+
+	if (sizeof(client->sndbuff.buf) - client->sndbuff.size < len) {
+		return -1;
+	}
+
+	memcpy(client->sndbuff.buf + client->sndbuff.size, packet, len);
+	client->sndbuff.size += len;
+
+	memset(&event, 0, sizeof(event));
+	event.events = EPOLLIN | EPOLLOUT;
+	event.data.ptr = client;
+	if (epoll_ctl(server.epoll_fd, EPOLL_CTL_MOD, client->fd, &event) != 0) {
+		tlog(TLOG_ERROR, "epoll ctl failed.");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int _dns_server_reply_tcp(struct dns_server_conn *client, void *packet, unsigned short len)
 {
 	int send_len = 0;
-	unsigned short *id = (unsigned short *)inpacket;
+	unsigned char inpacket_data[DNS_IN_PACKSIZE];
+	unsigned char *inpacket = inpacket_data;
+
+	/* TCP query format
+	 * | len (short) | dns query data | 
+	 */
+	*((unsigned short *)(inpacket)) = htons(len);
+	memcpy(inpacket + 2, packet, len);
+	len += 2;
+
+	send_len = send(client->fd, inpacket, len, MSG_NOSIGNAL);
+	if (send_len < 0) {
+		if (errno == EAGAIN) {
+			/* save data to buffer, and retry when EPOLLOUT is available */
+			return _dns_server_reply_tcp_to_buffer(client, inpacket, len);
+		}
+		return -1;
+	} else if (send_len < len) {
+		/* save remain data to buffer, and retry when EPOLLOUT is available */
+		return _dns_server_reply_tcp_to_buffer(client, inpacket + send_len, len - send_len);
+	}
 
-	*id = htons(request->id);
+	return 0;
+}
 
-	send_len = sendto(server.fd, inpacket, inpacket_len, 0, &request->addr, request->addr_len);
+static int _dns_server_reply_udp(struct dns_request *request, struct dns_server_conn *client, unsigned char *inpacket, int inpacket_len)
+{
+	int send_len = 0;
+	send_len = sendto(client->fd, inpacket, inpacket_len, 0, &request->addr, request->addr_len);
 	if (send_len != inpacket_len) {
 		tlog(TLOG_ERROR, "send failed.");
 		return -1;
@@ -258,6 +357,26 @@ static int _dns_reply_inpacket(struct dns_request *request, unsigned char *inpac
 	return 0;
 }
 
+static int _dns_reply_inpacket(struct dns_request *request, unsigned char *inpacket, int inpacket_len)
+{
+	struct dns_server_conn *client = request->client;
+	int ret = 0;
+
+	if (client->type == DNS_SERVER_UDP) {
+		ret = _dns_server_reply_udp(request, client, inpacket, inpacket_len);
+	} else if (client->type == DNS_SERVER_TCP) {
+		ret = _dns_server_reply_tcp(client, inpacket, inpacket_len);
+	} else if (client->type == DNS_SERVER_TLS) {
+		ret = -1;
+	} else {
+		ret = -1;
+	}
+
+	_dns_server_client_release(client);
+
+	return ret;
+}
+
 static int _dns_reply(struct dns_request *request)
 {
 	unsigned char inpacket[DNS_IN_PACKSIZE];
@@ -980,7 +1099,7 @@ errout:
 	return -1;
 }
 
-static int _dns_server_recv(unsigned char *inpacket, int inpacket_len, struct sockaddr_storage *from, socklen_t from_len)
+static int _dns_server_recv(struct dns_server_conn *client, unsigned char *inpacket, int inpacket_len, struct sockaddr_storage *from, socklen_t from_len)
 {
 	int decode_len;
 	int ret = -1;
@@ -994,6 +1113,8 @@ static int _dns_server_recv(unsigned char *inpacket, int inpacket_len, struct so
 	int qclass;
 	int qtype;
 
+	_dns_server_client_get(client);
+
 	decode_len = dns_decode(packet, DNS_PACKSIZE, inpacket, inpacket_len);
 	if (decode_len < 0) {
 		tlog(TLOG_ERROR, "decode failed.\n");
@@ -1016,6 +1137,7 @@ static int _dns_server_recv(unsigned char *inpacket, int inpacket_len, struct so
 	request->ping_ttl_v6 = -1;
 	request->prefetch = 0;
 	request->rcode = DNS_RC_SERVFAIL;
+	request->client = client;
 
 	if (_dns_recv_addr(request, from, from_len) != 0) {
 		goto errout;
@@ -1091,6 +1213,8 @@ errout:
 		ret = _dns_server_forward_request(inpacket, inpacket_len);
 		free(request);
 	}
+
+	_dns_server_client_release(client);
 	return ret;
 }
 
@@ -1133,20 +1257,252 @@ errout:
 	return ret;
 }
 
-static int _dns_server_process(unsigned long now)
+static int _dns_server_process_udp(struct dns_server_conn *dnsserver, struct epoll_event *event, unsigned long now)
 {
 	int len;
 	unsigned char inpacket[DNS_IN_PACKSIZE];
 	struct sockaddr_storage from;
 	socklen_t from_len = sizeof(from);
 
-	len = recvfrom(server.fd, inpacket, sizeof(inpacket), 0, (struct sockaddr *)&from, (socklen_t *)&from_len);
+	len = recvfrom(dnsserver->fd, inpacket, sizeof(inpacket), 0, (struct sockaddr *)&from, (socklen_t *)&from_len);
 	if (len < 0) {
 		tlog(TLOG_ERROR, "recvfrom failed, %s\n", strerror(errno));
 		return -1;
 	}
 
-	return _dns_server_recv(inpacket, len, &from, from_len);
+	return _dns_server_recv(dnsserver, inpacket, len, &from, from_len);
+}
+
+static void _dns_server_client_touch(struct dns_server_conn *client)
+{
+	time(&client->last_request_time);
+}
+
+static int _dns_server_client_close(struct dns_server_conn *client)
+{
+	if (client->fd > 0) {
+		close(client->fd);
+		client->fd = -1;
+	}
+
+	list_del_init(&client->list);
+
+	_dns_server_client_release(client);
+
+	return 0;
+}
+
+static int _dns_server_accept(struct dns_server_conn *dnsserver, struct epoll_event *event, unsigned long now)
+{
+	struct sockaddr_storage addr;
+	struct dns_server_conn *client = NULL;
+	socklen_t addr_len = sizeof(addr);
+	int fd = -1;
+	
+	fd = accept4(dnsserver->fd, (struct sockaddr *)&addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC);
+	if (fd < 0) {
+		return -1;
+	}
+
+	client = malloc(sizeof(*client));
+	if (client == NULL) {
+		goto errout;
+	}
+
+	memset(client, 0, sizeof(*client));
+	struct epoll_event event_client;
+	memset(&event_client, 0, sizeof(event_client));
+	event_client.data.ptr = client;
+	event_client.events = EPOLLIN;
+	if (epoll_ctl(server.epoll_fd, EPOLL_CTL_ADD, fd, &event_client) != 0) {
+		tlog(TLOG_ERROR, "epoll add failed, %s", strerror(errno));
+		goto errout;
+	}
+
+	client->fd = fd;
+	client->type = DNS_SERVER_TCP;
+	atomic_set(&client->refcnt, 0);
+	memcpy(&client->addr, &addr, addr_len);
+	client->addr_len = addr_len;
+
+	_dns_server_client_touch(client);
+
+	list_add(&client->list, &server.client_list);
+	_dns_server_client_get(client);
+
+	return 0;
+errout:
+	if (fd > 0) {
+		close(fd);
+	}
+	if (client) {
+		free(client);
+	}
+	return -1;
+}
+
+int _dns_server_tcp_recv(struct dns_server_conn *dnsserver)
+{
+	int len = 0;
+
+	while (dnsserver->recvbuff.size < sizeof(dnsserver->recvbuff.buf)) {
+		if (dnsserver->recvbuff.size == sizeof(dnsserver->recvbuff.buf)) {
+			return 0;
+		}
+
+		len = recv(dnsserver->fd, dnsserver->recvbuff.buf + dnsserver->recvbuff.size, 
+			sizeof(dnsserver->recvbuff.buf) - dnsserver->recvbuff.size, 0);
+		if (len < 0) {
+			if (errno == EAGAIN) {
+				return 1;
+			}
+
+			return -1;
+		} else if (len == 0) {
+			return -1;
+		}
+
+		dnsserver->recvbuff.size += len;
+	}
+
+	return 0;
+}
+
+int _dns_server_tcp_process_one_request(struct dns_server_conn *dnsserver)
+{
+	int request_len = 0;
+	int total_len = dnsserver->recvbuff.size;
+	int proceed_len = 0;
+	unsigned char *request_data = NULL;
+
+	for (;;) {
+		if ((total_len - proceed_len) <= sizeof(unsigned short)) {
+			return 1;
+		}
+
+		request_len = ntohs(*((unsigned short *)(dnsserver->recvbuff.buf + proceed_len)));
+		if (request_len > (total_len - proceed_len)) {
+			return 1;
+		}
+
+		if (request_len > 4096) {
+			tlog(TLOG_ERROR, "request length is invalid.");
+			return -1;
+		}
+
+		request_data = (unsigned char *)(dnsserver->recvbuff.buf + proceed_len + sizeof(unsigned short));
+
+		if (_dns_server_recv(dnsserver, request_data, request_len, &dnsserver->addr, dnsserver->addr_len) != 0) {
+			tlog(TLOG_ERROR, "process tcp request failed.");
+			return -1;
+		}
+
+		proceed_len += sizeof(unsigned short) + request_len;
+	}
+
+	if (total_len > proceed_len && proceed_len > 0) {
+		memmove(dnsserver->recvbuff.buf, dnsserver->recvbuff.buf + proceed_len, total_len - proceed_len);
+	}
+
+	dnsserver->recvbuff.size -= proceed_len;
+
+	return 0;
+}
+
+int _dns_server_tcp_process_requests(struct dns_server_conn *client)
+{
+	int recv_ret = 0;
+	int request_ret = 0;
+	int is_eof = 0;
+
+	for (;;) {
+		recv_ret = _dns_server_tcp_recv(client);
+		if (recv_ret < 0) {
+			if (client->recvbuff.size > 0) {
+				is_eof = 1;
+			} else {
+				return -1;
+			}
+		}
+
+		request_ret = _dns_server_tcp_process_one_request(client);
+		if (request_ret < 0) {
+			return -1;
+		}
+
+		if (request_ret == 1 && is_eof == 1) {
+			return -1;
+		}
+
+		if (recv_ret == 1 && request_ret == 1) {
+			return 0;
+		}
+	}
+
+	return 0;
+}
+
+int _dns_server_tcp_send(struct dns_server_conn *client)
+{
+	int len;
+	while (client->sndbuff.size > 0) {
+		len = send(client->fd, client->sndbuff.buf, client->sndbuff.size, MSG_NOSIGNAL);
+		if (len < 0) {
+			if (errno == EAGAIN) {
+				return 1;
+			}
+			return -1;
+		} else if (len == 0 ) {
+			break;
+		}
+
+		client->sndbuff.size -= len;
+	}
+
+	struct epoll_event event_client;
+	event_client.data.ptr = client;
+	event_client.events = EPOLLIN;
+	if (epoll_ctl(server.epoll_fd, EPOLL_CTL_MOD, client->fd, &event_client) != 0) {
+		tlog(TLOG_ERROR, "epoll add failed, %s", strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int _dns_server_process_tcp(struct dns_server_conn *dnsserver, struct epoll_event *event, unsigned long now)
+{
+	if (dnsserver == &server.tcp_server) {
+		return _dns_server_accept(dnsserver, event, now);
+	}
+
+	if (event->events & EPOLLIN) {
+		if (_dns_server_tcp_process_requests(dnsserver) != 0) {
+			_dns_server_client_close(dnsserver);
+		}
+	} 
+
+	if (event->events & EPOLLOUT) {
+		if (_dns_server_tcp_send(dnsserver) != 0) {
+			_dns_server_client_close(dnsserver);
+		}
+	}
+
+	return 0;
+}
+
+static int _dns_server_process(struct dns_server_conn *dnsserver, struct epoll_event *event, unsigned long now)
+{
+	_dns_server_client_touch(dnsserver);
+	if (dnsserver->type == DNS_SERVER_UDP) {
+		return _dns_server_process_udp(dnsserver, event, now);
+	} else if (dnsserver->type == DNS_SERVER_TCP) {
+		return _dns_server_process_tcp(dnsserver, event, now);
+	} else if (dnsserver->type == DNS_SERVER_TLS) {
+		return -1;
+	} else {
+		return -1;
+	}
 }
 
 void _dns_server_tcp_ping_check(struct dns_request *request)
@@ -1203,6 +1559,27 @@ void _dns_server_prefetch_domain(struct dns_cache *dns_cache)
 	}
 }
 
+void _dns_server_tcp_idle_check(void)
+{
+	struct dns_server_conn *client, *tmp;
+	time_t now;
+
+	if (dns_conf_tcp_idle_time <= 0) {
+		return;
+	}
+
+	time(&now);
+	list_for_each_entry_safe(client, tmp, &server.client_list, list)
+	{
+		if (client->last_request_time > now - dns_conf_tcp_idle_time) {
+			continue;
+		}
+
+		_dns_server_client_close(client);
+	}
+}
+
+
 void _dns_server_period_run_second(void)
 {
 	static unsigned int sec = 0;
@@ -1215,6 +1592,8 @@ void _dns_server_period_run_second(void)
 			dns_cache_invalidate(NULL, 0);
 		}
 	}
+
+	_dns_server_tcp_idle_check();
 }
 
 void _dns_server_period_run(void)
@@ -1269,6 +1648,7 @@ int dns_server_run(void)
 			sleep_time = sleep - (now - expect_time);
 			if (sleep_time < 0) {
 				sleep_time = 0;
+				expect_time = now;
 			}
 			expect_time += sleep;
 		}
@@ -1284,12 +1664,13 @@ int dns_server_run(void)
 		}
 		for (i = 0; i < num; i++) {
 			struct epoll_event *event = &events[i];
-			if (event->data.fd != server.fd) {
+			struct dns_server_conn *dnsserver = event->data.ptr;
+			if (dnsserver == NULL) {
 				tlog(TLOG_ERROR, "invalid fd\n");
 				continue;
 			}
 
-			if (_dns_server_process(now) != 0) {
+			if (_dns_server_process(dnsserver, event, now) != 0) {
 				tlog(TLOG_ERROR, "dns server process failed.");
 			}
 		}
@@ -1324,13 +1705,37 @@ errout:
 	return NULL;
 }
 
-int dns_server_start(void)
+int _dns_server_start_udp(void)
+{
+	struct epoll_event event;
+
+	if (server.udp_server.fd <= 0) {
+		return 0;
+	}
+	
+	memset(&event, 0, sizeof(event));
+	event.events = EPOLLIN;
+	event.data.ptr = &server.udp_server;
+	if (epoll_ctl(server.epoll_fd, EPOLL_CTL_ADD, server.udp_server.fd, &event) != 0) {
+		tlog(TLOG_ERROR, "epoll ctl failed.");
+		return -1;
+	}
+
+	return 0;
+}
+
+int _dns_server_start_tcp(void)
 {
 	struct epoll_event event;
+
+	if (server.tcp_server.fd <= 0) {
+		return 0;
+	}
+	
 	memset(&event, 0, sizeof(event));
 	event.events = EPOLLIN;
-	event.data.fd = server.fd;
-	if (epoll_ctl(server.epoll_fd, EPOLL_CTL_ADD, server.fd, &event) != 0) {
+	event.data.ptr = &server.tcp_server;
+	if (epoll_ctl(server.epoll_fd, EPOLL_CTL_ADD, server.tcp_server.fd, &event) != 0) {
 		tlog(TLOG_ERROR, "epoll ctl failed.");
 		return -1;
 	}
@@ -1338,7 +1743,22 @@ int dns_server_start(void)
 	return 0;
 }
 
-int dns_server_socket(void)
+int dns_server_start(void)
+{
+	if (_dns_server_start_udp() != 0) {
+		tlog(TLOG_ERROR, "start udp server failed.");
+		return -1;
+	}
+
+	if (_dns_server_start_tcp() != 0) {
+		tlog(TLOG_ERROR, "start tcp server failed.");
+		return -1;
+	}
+	return 0;
+}
+
+
+int _dns_create_socket(const char *host_ip, int type)
 {
 	int fd = -1;
 	struct addrinfo *gai = NULL;
@@ -1348,7 +1768,7 @@ int dns_server_socket(void)
 	char *host = NULL;
 	int optval = 1;
 
-	if (parse_ip(dns_conf_server_ip, ip, &port) == 0) {
+	if (parse_ip(host_ip, ip, &port) == 0) {
 		host = ip;
 	}
 
@@ -1357,7 +1777,7 @@ int dns_server_socket(void)
 	}
 
 	snprintf(port_str, sizeof(port_str), "%d", port);
-	gai = _dns_server_getaddr(host, port_str, SOCK_DGRAM, 0);
+	gai = _dns_server_getaddr(host, port_str, type, 0);
 	if (gai == NULL) {
 		tlog(TLOG_ERROR, "get address failed.\n");
 		goto errout;
@@ -1379,7 +1799,13 @@ int dns_server_socket(void)
 		goto errout;
 	}
 
-	server.fd = fd;
+	if (type == SOCK_STREAM) {
+		if (listen(fd, 16) != 0) {
+			tlog(TLOG_ERROR, "listen failed.\n");
+			goto errout;
+		}
+	}
+
 	freeaddrinfo(gai);
 
 	return fd;
@@ -1394,6 +1820,60 @@ errout:
 	return -1;
 }
 
+int _dns_server_socket(void)
+{
+	int fd_udp = -1;
+	int fd_tcp = -1;
+
+	if (dns_conf_server_ip[0] != 0) {
+		fd_udp = _dns_create_socket(dns_conf_server_ip, SOCK_DGRAM);
+		if (fd_udp < 0) {
+			goto errout;
+		}
+	}
+
+	if (dns_conf_server_tcp_ip[0] != 0) {
+		fd_tcp = _dns_create_socket(dns_conf_server_tcp_ip, SOCK_STREAM);
+		if (fd_tcp < 0) {
+			goto errout;
+		}
+
+	}
+
+	server.udp_server.fd = fd_udp;
+	server.udp_server.type = DNS_SERVER_UDP;
+	_dns_server_client_get(&server.udp_server);
+	INIT_LIST_HEAD(&server.udp_server.list);
+	server.tcp_server.fd = fd_tcp;
+	server.tcp_server.type = DNS_SERVER_TCP;
+	INIT_LIST_HEAD(&server.tcp_server.list);
+	_dns_server_client_get(&server.tcp_server);
+	return 0;
+errout:
+	if (fd_udp > 0) {
+		close(fd_udp);
+	}
+
+	if (fd_tcp > 0) {
+		close(fd_tcp);
+	}
+
+	return -1;
+}
+
+void _dns_server_close_socket(void)
+{
+	if (server.udp_server.fd > 0) {
+		close(server.udp_server.fd);
+		server.udp_server.fd = 0;
+	}
+
+	if (server.tcp_server.fd > 0) {
+		close(server.tcp_server.fd);
+		server.tcp_server.fd = 0;
+	}
+}
+
 int _dns_server_audit_init(void)
 {
 	char *audit_file = SMARTDNS_AUDIT_FILE;
@@ -1417,7 +1897,7 @@ int dns_server_init(void)
 {
 	pthread_attr_t attr;
 	int epollfd = -1;
-	int fd = -1;
+	int ret = -1;
 
 	if (server.epoll_fd > 0) {
 		return -1;
@@ -1435,6 +1915,7 @@ int dns_server_init(void)
 
 	memset(&server, 0, sizeof(server));
 	pthread_attr_init(&attr);
+	INIT_LIST_HEAD(&server.client_list);
 
 	epollfd = epoll_create1(EPOLL_CLOEXEC);
 	if (epollfd < 0) {
@@ -1442,8 +1923,8 @@ int dns_server_init(void)
 		goto errout;
 	}
 
-	fd = dns_server_socket();
-	if (fd < 0) {
+	ret = _dns_server_socket();
+	if (ret != 0) {
 		tlog(TLOG_ERROR, "create server socket failed.\n");
 		goto errout;
 	}
@@ -1451,7 +1932,6 @@ int dns_server_init(void)
 	pthread_mutex_init(&server.request_list_lock, 0);
 	INIT_LIST_HEAD(&server.request_list);
 	server.epoll_fd = epollfd;
-	server.fd = fd;
 	server.run = 1;
 
 	if (dns_server_start() != 0) {
@@ -1463,14 +1943,11 @@ int dns_server_init(void)
 errout:
 	server.run = 0;
 
-	if (fd > 0) {
-		close(fd);
-	}
-
 	if (epollfd) {
 		close(epollfd);
 	}
 
+	_dns_server_close_socket();
 	pthread_mutex_destroy(&server.request_list_lock);
 
 	dns_cache_destroy();
@@ -1489,12 +1966,7 @@ void dns_server_exit(void)
 	LIST_HEAD(remove_list);
 
 	server.run = 0;
-
-	if (server.fd > 0) {
-		close(server.fd);
-		server.fd = -1;
-	}
-
+	_dns_server_close_socket();
 	pthread_mutex_lock(&server.request_list_lock);
 	list_for_each_entry_safe(request, tmp, &server.request_list, list)
 	{

+ 3 - 2
src/fast_ping.c

@@ -474,7 +474,7 @@ static int _fast_ping_create_icmp_sock(FAST_PING_TYPE type)
 	case FAST_PING_ICMP:
 		fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
 		if (fd < 0) {
-			tlog(TLOG_ERROR, "create icmp socket failed.\n");
+			tlog(TLOG_ERROR, "create icmp socket failed, %s\n", strerror(errno));
 			goto errout;
 		}
 		_fast_ping_install_filter_v4(fd);
@@ -483,7 +483,7 @@ static int _fast_ping_create_icmp_sock(FAST_PING_TYPE type)
 	case FAST_PING_ICMP6:
 		fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
 		if (fd < 0) {
-			tlog(TLOG_ERROR, "create icmp socket failed.\n");
+			tlog(TLOG_ERROR, "create icmp socket failed, %s\n", strerror(errno));
 			goto errout;
 		}
 		_fast_ping_install_filter_v6(fd);
@@ -1032,6 +1032,7 @@ static void *_fast_ping_work(void *arg)
 			sleep_time = sleep - (now - expect_time);
 			if (sleep_time < 0) {
 				sleep_time = 0;
+				expect_time = now;
 			}
 			expect_time += sleep;
 		}

+ 29 - 13
src/tlog.c

@@ -835,6 +835,11 @@ static int _tlog_write_log(struct tlog_log *log, char *buff, int bufflen)
         return 0;
     }
 
+     /* output log to screen */
+    if (log->logscreen) {
+        write(STDOUT_FILENO, buff, bufflen);
+    }
+
     /* if log file size exceeds threshold, start to compress */
     if (log->multi_log) {
         log->filesize = lseek(log->fd, 0, SEEK_END);
@@ -854,8 +859,18 @@ static int _tlog_write_log(struct tlog_log *log, char *buff, int bufflen)
 
     if (log->fd <= 0) {
         /* open a new log file to write */
-        char logfile[PATH_MAX * 2];
-        if (_tlog_mkdir(log->logdir) != 0) {
+		static time_t last_try = 0;
+		static int print_errmsg = 1;
+		time_t now;
+
+		time(&now);
+        if (now == last_try) {
+			return -1;
+		}
+		last_try = now;
+
+		char logfile[PATH_MAX * 2];
+		if (_tlog_mkdir(log->logdir) != 0) {
             fprintf(stderr, "create log dir %s failed.\n", log->logdir);
             return -1;
         }
@@ -863,17 +878,18 @@ static int _tlog_write_log(struct tlog_log *log, char *buff, int bufflen)
         log->filesize = 0;
         log->fd = open(logfile, O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, 0640);
         if (log->fd < 0) {
-            fprintf(stderr, "open log file %s failed, %s\n", logfile, strerror(errno));
-            return -1;
-        }
-
-        /* get log file size */
-        log->filesize = lseek(log->fd, 0, SEEK_END);
-    }
-
-    /* output log to screen */
-    if (log->logscreen) {
-        write(STDOUT_FILENO, buff, bufflen);
+            if (print_errmsg == 0) {
+				return -1;
+			}
+
+			fprintf(stderr, "open log file %s failed, %s\n", logfile, strerror(errno));
+			print_errmsg = 0;
+			return -1;
+		}
+
+		print_errmsg = 1;
+		/* get log file size */
+		log->filesize = lseek(log->fd, 0, SEEK_END);
     }
 
     /* write log to file */