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"); +}