diff --git a/src/Makefile b/src/Makefile
index 15e92b10e1..ac9fb9c557 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -15,8 +15,8 @@
# along with this program. If not, see .
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
diff --git a/src/dns_conf.c b/src/dns_conf.c
index dab3e59fc2..9da3adfe82 100644
--- a/src/dns_conf.c
+++ b/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;
diff --git a/src/include/idna.h b/src/include/idna.h
new file mode 100644
index 0000000000..66272f658e
--- /dev/null
+++ b/src/include/idna.h
@@ -0,0 +1,32 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) .
+ *
+ * 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 .
+ */
+
+#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
diff --git a/src/lib/idna.c b/src/lib/idna.c
new file mode 100644
index 0000000000..3193b20cbb
--- /dev/null
+++ b/src/lib/idna.c
@@ -0,0 +1,339 @@
+
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) .
+ *
+ * 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 .
+ */
+
+#define _GNU_SOURCE
+#include "idna.h"
+#include
+
+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;
+}
\ No newline at end of file
diff --git a/src/tlog.c b/src/tlog.c
index 7b7a7ba733..8df92a9fae 100644
--- a/src/tlog.c
+++ b/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);
}
diff --git a/test/Makefile b/test/Makefile
index 3449f598cc..6898cc225d 100644
--- a/test/Makefile
+++ b/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))
diff --git a/test/cases/test-idna.cc b/test/cases/test-idna.cc
new file mode 100644
index 0000000000..f09793e240
--- /dev/null
+++ b/test/cases/test-idna.cc
@@ -0,0 +1,77 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2023 Ruilin Peng (Nick) .
+ *
+ * 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 .
+ */
+
+#include "client.h"
+#include "dns.h"
+#include "include/utils.h"
+#include "server.h"
+#include "util.h"
+#include "gtest/gtest.h"
+#include
+
+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");
+}