Nick Peng 2 лет назад
Родитель
Сommit
9554b3debe
7 измененных файлов с 461 добавлено и 6 удалено
  1. 2 2
      src/Makefile
  2. 9 2
      src/dns_conf.c
  3. 32 0
      src/include/idna.h
  4. 339 0
      src/lib/idna.c
  5. 1 1
      src/tlog.c
  6. 1 1
      test/Makefile
  7. 77 0
      test/cases/test-idna.cc

+ 2 - 2
src/Makefile

@@ -15,8 +15,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 BIN=smartdns 
-OBJS_LIB=lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o lib/timer_wheel.o
-OBJS_MAIN=smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_conf.o dns_cache.o http_parse.o proxy.o timer.o lib/conf.o lib/nftset.o
+OBJS_LIB=lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o lib/timer_wheel.o lib/idna.o lib/conf.o lib/nftset.o
+OBJS_MAIN=smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_conf.o dns_cache.o http_parse.o proxy.o timer.o
 OBJS=$(OBJS_MAIN) $(OBJS_LIB)
 
 # cflags

+ 9 - 2
src/dns_conf.c

@@ -17,6 +17,7 @@
  */
 
 #include "dns_conf.h"
+#include "idna.h"
 #include "list.h"
 #include "rbtree.h"
 #include "tlog.h"
@@ -308,8 +309,14 @@ static int _get_domain(char *value, char *domain, int max_domain_size, char **pt
 		goto errout;
 	}
 
-	memcpy(domain, begin, len);
-	domain[len] = '\0';
+	size_t domain_len = max_domain_size;
+	domain_len = utf8_to_punycode(begin, len, domain, domain_len);
+	if (domain_len <= 0) {
+		tlog(TLOG_ERROR, "domain name %s invalid", value);
+		goto errout;
+	}
+
+	domain[domain_len] = '\0';
 
 	if (ptr_after_domain) {
 		*ptr_after_domain = end + 1;

+ 32 - 0
src/include/idna.h

@@ -0,0 +1,32 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
+ *
+ * smartdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * smartdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SMARTDNS_IDNA_H
+#define _SMARTDNS_IDNA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int utf8_to_punycode(const char *src, int src_len, char *dst, int dst_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // !_SMARTDNS_IDNA_H

+ 339 - 0
src/lib/idna.c

@@ -0,0 +1,339 @@
+
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
+ *
+ * smartdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * smartdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+#include "idna.h"
+#include <limits.h>
+
+static unsigned _utf8_decode_slow(const char **p, const char *pe, unsigned a)
+{
+	unsigned b;
+	unsigned c;
+	unsigned d;
+	unsigned min;
+
+	if (a > 0xF7) {
+		return -1;
+	}
+
+	switch (pe - *p) {
+	default:
+		if (a > 0xEF) {
+			min = 0x10000;
+			a = a & 7;
+			b = (unsigned char)*(*p)++;
+			c = (unsigned char)*(*p)++;
+			d = (unsigned char)*(*p)++;
+			break;
+		}
+	case 2:
+		if (a > 0xDF) {
+			min = 0x800;
+			b = 0x80 | (a & 15);
+			c = (unsigned char)*(*p)++;
+			d = (unsigned char)*(*p)++;
+			a = 0;
+			break;
+		}
+	case 1:
+		if (a > 0xBF) {
+			min = 0x80;
+			b = 0x80;
+			c = 0x80 | (a & 31);
+			d = (unsigned char)*(*p)++;
+			a = 0;
+			break;
+		}
+	case 0:
+		return -1;
+	}
+
+	if (0x80 != (0xC0 & (b ^ c ^ d))) {
+		return -1;
+	}
+
+	b &= 63;
+	c &= 63;
+	d &= 63;
+	a = (a << 18) | (b << 12) | (c << 6) | d;
+
+	if (a < min) {
+		return -1;
+	}
+
+	if (a > 0x10FFFF) {
+		return -1;
+	}
+
+	if (a >= 0xD800 && a <= 0xDFFF) {
+		return -1;
+	}
+
+	return a;
+}
+
+static unsigned _utf8_decode(const char **p, const char *pe)
+{
+	unsigned a;
+
+	a = (unsigned char)*(*p)++;
+
+	if (a < 128) {
+		return a;
+	}
+
+	return _utf8_decode_slow(p, pe, a);
+}
+
+static int _utf8_to_punycode_label(const char *s, const char *se, char **d, char *de)
+{
+	static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+	const char *ss;
+	unsigned c;
+	unsigned h;
+	unsigned k;
+	unsigned n;
+	unsigned m;
+	unsigned q;
+	unsigned t;
+	unsigned x;
+	unsigned y;
+	unsigned bias;
+	unsigned delta;
+	unsigned todo;
+	int first;
+
+	h = 0;
+	ss = s;
+	todo = 0;
+
+	while (s < se) {
+		c = _utf8_decode(&s, se);
+		if (c == UINT_MAX) {
+			return -1;
+		}
+
+		if (c < 128) {
+			h++;
+		} else {
+			todo++;
+		}
+	}
+
+	if (todo > 0) {
+		if (*d < de) {
+			*(*d)++ = 'x';
+		}
+		if (*d < de) {
+			*(*d)++ = 'n';
+		}
+		if (*d < de) {
+			*(*d)++ = '-';
+		}
+		if (*d < de) {
+			*(*d)++ = '-';
+		}
+	}
+
+	x = 0;
+	s = ss;
+	while (s < se) {
+		c = _utf8_decode(&s, se);
+
+		if (c > 127) {
+			continue;
+		}
+
+		if (*d < de) {
+			*(*d)++ = c;
+		}
+
+		if (++x == h) {
+			break;
+		}
+	}
+
+	if (todo == 0) {
+		return h;
+	}
+
+	if (h > 0) {
+		if (*d < de) {
+			*(*d)++ = '-';
+		}
+	}
+
+	n = 128;
+	bias = 72;
+	delta = 0;
+	first = 1;
+
+	while (todo > 0) {
+		m = -1;
+		s = ss;
+
+		while (s < se) {
+			c = _utf8_decode(&s, se);
+
+			if (c >= n) {
+				if (c < m) {
+					m = c;
+				}
+			}
+		}
+
+		x = m - n;
+		y = h + 1;
+
+		if (x > ~delta / y) {
+			return -1;
+		}
+
+		delta += x * y;
+		n = m;
+
+		s = ss;
+		while (s < se) {
+			c = _utf8_decode(&s, se);
+
+			if (c < n) {
+				if (++delta == 0) {
+					return -1;
+				}
+			}
+
+			if (c != n) {
+				continue;
+			}
+
+			for (k = 36, q = delta;; k += 36) {
+				t = 1;
+
+				if (k > bias) {
+					t = k - bias;
+				}
+
+				if (t > 26) {
+					t = 26;
+				}
+
+				if (q < t) {
+					break;
+				}
+
+				x = q - t;
+				y = 36 - t;
+				q = x / y;
+				t = t + x % y;
+
+				if (*d < de) {
+					*(*d)++ = alphabet[t];
+				}
+			}
+
+			if (*d < de) {
+				*(*d)++ = alphabet[q];
+			}
+
+			delta /= 2;
+
+			if (first) {
+				delta /= 350;
+				first = 0;
+			}
+
+			h++;
+			delta += delta / h;
+
+			for (bias = 0; delta > 35 * 26 / 2; bias += 36) {
+				delta /= 35;
+			}
+
+			bias += 36 * delta / (delta + 38);
+			delta = 0;
+			todo--;
+		}
+
+		delta++;
+		n++;
+	}
+
+	return 0;
+}
+
+int utf8_to_punycode(const char *src, int src_len, char *dst, int dst_len)
+{
+	const char *si;
+	const char *se;
+	const char *st;
+	unsigned c;
+	char *ds;
+	char *de;
+	int rc;
+
+	ds = dst;
+	si = src;
+	se = src + src_len;
+	de = dst + dst_len;
+
+	while (si < se) {
+		st = si;
+		c = _utf8_decode(&si, se);
+
+		if (c == UINT_MAX) {
+			return -1;
+		}
+
+		if (c != '.') {
+			if (c != 0x3002) {
+				if (c != 0xFF0E) {
+					if (c != 0xFF61) {
+						continue;
+					}
+				}
+			}
+		}
+
+		rc = _utf8_to_punycode_label(src, st, &dst, de);
+
+		if (rc < 0) {
+			return rc;
+		}
+
+		if (dst < de) {
+			*dst++ = '.';
+		}
+
+		src = si;
+	}
+
+	if (src < se) {
+		rc = _utf8_to_punycode_label(src, se, &dst, de);
+
+		if (rc < 0) {
+			return rc;
+		}
+	}
+
+	if (dst < de) {
+		*dst++ = '\0';
+	}
+
+	return dst - ds;
+}

+ 1 - 1
src/tlog.c

@@ -1187,7 +1187,7 @@ static int _tlog_write_screen(struct tlog_log *log, struct tlog_loginfo *info, c
         }
 
         if (color != NULL) {
-            fprintf(stdout, "%s%s\e[0m", color, buff);
+            fprintf(stdout, "%s%.*s\033[0m\n", color, bufflen - 2, buff);
         } else {
             fprintf(stdout, "%s", buff);
         }

+ 1 - 1
test/Makefile

@@ -23,7 +23,7 @@ CXXFLAGS += -g
 CXXFLAGS += -DTEST
 CXXFLAGS += -I./ -I../src -I../src/include
 
-SMARTDNS_OBJS = lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o lib/conf.o lib/nftset.o lib/timer_wheel.o
+SMARTDNS_OBJS = lib/rbtree.o lib/art.o lib/bitops.o lib/radix.o lib/conf.o lib/nftset.o lib/timer_wheel.o lib/idna.o
 SMARTDNS_OBJS += smartdns.o fast_ping.o dns_client.o dns_server.o dns.o util.o tlog.o dns_conf.o dns_cache.o http_parse.o proxy.o timer.o
 OBJS = $(addprefix ../src/, $(SMARTDNS_OBJS))
 

+ 77 - 0
test/cases/test-idna.cc

@@ -0,0 +1,77 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) <[email protected]>.
+ *
+ * smartdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * smartdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "client.h"
+#include "dns.h"
+#include "include/utils.h"
+#include "server.h"
+#include "util.h"
+#include "gtest/gtest.h"
+#include <fstream>
+
+class IDNA : public ::testing::Test
+{
+  protected:
+	virtual void SetUp() {}
+	virtual void TearDown() {}
+};
+
+TEST_F(IDNA, match)
+{
+	smartdns::MockServer server_upstream;
+	smartdns::Server server;
+
+	server_upstream.Start("udp://0.0.0.0:61053", [&](struct smartdns::ServerRequestContext *request) {
+		if (request->qtype == DNS_T_A) {
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "1.2.3.4", 700);
+			return smartdns::SERVER_REQUEST_OK;
+		} else if (request->qtype == DNS_T_AAAA) {
+			smartdns::MockServer::AddIP(request, request->domain.c_str(), "64:ff9b::102:304", 700);
+			return smartdns::SERVER_REQUEST_OK;
+		}
+		return smartdns::SERVER_REQUEST_SOA;
+	});
+
+	server.Start(R"""(bind [::]:60053
+server 127.0.0.1:61053
+log-num 0
+log-console yes
+log-level debug
+speed-check-mode none
+address /中国.com/10.10.10.10
+address /中国.com/64:ff9b::1010:1010
+cache-persist no)""");
+	smartdns::Client client;
+	ASSERT_TRUE(client.Query("xn--fiqs8s.com A", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "xn--fiqs8s.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "A");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "10.10.10.10");
+
+	ASSERT_TRUE(client.Query("xn--fiqs8s.com AAAA", 60053));
+	std::cout << client.GetResult() << std::endl;
+	ASSERT_EQ(client.GetAnswerNum(), 1);
+	EXPECT_EQ(client.GetStatus(), "NOERROR");
+	EXPECT_EQ(client.GetAnswer()[0].GetName(), "xn--fiqs8s.com");
+	EXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);
+	EXPECT_EQ(client.GetAnswer()[0].GetType(), "AAAA");
+	EXPECT_EQ(client.GetAnswer()[0].GetData(), "64:ff9b::1010:1010");
+}