From 3d8b33f0e8f20e2ad761c3582bdf2aeaf3154978 Mon Sep 17 00:00:00 2001 From: Datong Sun Date: Fri, 23 Feb 2018 18:39:33 -0800 Subject: [PATCH] feature: added a patch for the nginx core to add the "local=/path/to/resolv.conf" option to the standard "resolver" config directive. This can enable the use of system-level nameserver configurations of /etc/resolv.conf, for example, in nginx's own nonblocking DNS resolver. Signed-off-by: Yichun Zhang (agentzh) --- .gitignore | 1 + .../nginx-1.13.6-resolver_conf_parsing.patch | 248 +++++++++++++ t/001-resolver.t | 345 ++++++++++++++++++ util/mirror-tarballs | 4 + valgrind.suppress | 209 +++++++++++ 5 files changed, 807 insertions(+) create mode 100644 patches/nginx-1.13.6-resolver_conf_parsing.patch create mode 100644 t/001-resolver.t create mode 100644 valgrind.suppress diff --git a/.gitignore b/.gitignore index db4d599..9c11b4b 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,4 @@ upload-win32 html_out/ TODO doc/LuaJIT-2.1/changes.pod +t/servroot diff --git a/patches/nginx-1.13.6-resolver_conf_parsing.patch b/patches/nginx-1.13.6-resolver_conf_parsing.patch new file mode 100644 index 0000000..cb89a21 --- /dev/null +++ b/patches/nginx-1.13.6-resolver_conf_parsing.patch @@ -0,0 +1,248 @@ +diff --git a/src/core/ngx_resolver.c b/src/core/ngx_resolver.c +index cd55520c..d8dc49e8 100644 +--- a/src/core/ngx_resolver.c ++++ b/src/core/ngx_resolver.c +@@ -5,6 +5,7 @@ + */ + + ++#include + #include + #include + #include +@@ -15,6 +16,14 @@ + #define NGX_RESOLVER_TCP_RSIZE (2 + 65535) + #define NGX_RESOLVER_TCP_WSIZE 8192 + ++/* ++ * note that 2KB should be more than enough for majority of the ++ * resolv.conf files out there. it also acts as a safety guard to prevent ++ * abuse. ++ */ ++#define NGX_RESOLVER_FILE_BUF_SIZE 2048 ++#define NGX_RESOLVER_FILE_NAME "/etc/resolv.conf" ++ + + typedef struct { + u_char ident_hi; +@@ -131,6 +140,182 @@ static ngx_resolver_node_t *ngx_resolver_lookup_addr6(ngx_resolver_t *r, + #endif + + ++static ngx_int_t ++ngx_resolver_read_resolv_conf(ngx_conf_t *cf, ngx_resolver_t *r, u_char *path, ++ size_t path_len) ++{ ++ ngx_url_t u; ++ ngx_resolver_connection_t *rec; ++ ngx_fd_t fd; ++ ngx_file_t file; ++ u_char buf[NGX_RESOLVER_FILE_BUF_SIZE]; ++ u_char ipv6_buf[NGX_INET6_ADDRSTRLEN]; ++ ngx_uint_t address = 0, j, total = 0; ++ ssize_t n, i; ++ enum { ++ sw_nameserver, ++ sw_spaces, ++ sw_address, ++ sw_skip ++ } state; ++ ++ file.name.data = path; ++ file.name.len = path_len; ++ ++ if (ngx_conf_full_name(cf->cycle, &file.name, 1) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, ++ NGX_FILE_OPEN, 0); ++ ++ if (fd == NGX_INVALID_FILE) { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, ++ ngx_open_file_n " \"%s\" failed", file.name.data); ++ ++ return NGX_ERROR; ++ } ++ ++ ngx_memzero(&file, sizeof(ngx_file_t)); ++ ++ file.fd = fd; ++ file.log = cf->log; ++ ++ state = sw_nameserver; ++ ++ n = ngx_read_file(&file, buf, NGX_RESOLVER_FILE_BUF_SIZE, 0); ++ ++ if (n == NGX_ERROR) { ++ ngx_conf_log_error(NGX_LOG_ALERT, cf, ngx_errno, ++ ngx_read_file_n " \"%s\" failed", file.name.data); ++ } ++ ++ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { ++ ngx_conf_log_error(NGX_LOG_ALERT, cf, ngx_errno, ++ ngx_close_file_n " \"%s\" failed", file.name.data); ++ } ++ ++ if (n == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (n == 0) { ++ return NGX_OK; ++ } ++ ++ for (i = 0; i < n && total < MAXNS; /* void */) { ++ if (buf[i] == '#' || buf[i] == ';') { ++ state = sw_skip; ++ } ++ ++ switch (state) { ++ ++ case sw_nameserver: ++ ++ if ((size_t) n - i >= sizeof("nameserver") - 1 ++ && ngx_memcmp(buf + i, "nameserver", ++ sizeof("nameserver") - 1) == 0) ++ { ++ state = sw_spaces; ++ i += sizeof("nameserver") - 1; ++ ++ continue; ++ } ++ ++ break; ++ ++ case sw_spaces: ++ if (buf[i] != '\t' && buf[i] != ' ') { ++ address = i; ++ state = sw_address; ++ } ++ ++ break; ++ ++ case sw_address: ++ ++ if (buf[i] == CR || buf[i] == LF || i == n - 1) { ++ ngx_memzero(&u, sizeof(ngx_url_t)); ++ ++ u.url.data = buf + address; ++ u.url.len = (i == n - 1) ? n - address : i - address; ++ u.default_port = 53; ++ ++ /* IPv6? */ ++ if (ngx_strlchr(u.url.data, u.url.data + u.url.len, ++ ':') != NULL) ++ { ++ if (u.url.len + 2 > sizeof(ipv6_buf)) { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "IPv6 resolver address is too long:" ++ " \"%V\"", &u.url); ++ ++ return NGX_ERROR; ++ } ++ ++ ipv6_buf[0] = '['; ++ ngx_memcpy(ipv6_buf + 1, u.url.data, u.url.len); ++ ipv6_buf[u.url.len + 1] = ']'; ++ ++ u.url.data = ipv6_buf; ++ u.url.len = u.url.len + 2; ++ } ++ ++ if (ngx_parse_url(cf->pool, &u) != NGX_OK) { ++ if (u.err) { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "%s in resolver \"%V\"", ++ u.err, &u.url); ++ } ++ ++ return NGX_ERROR; ++ } ++ ++ rec = ngx_array_push_n(&r->connections, u.naddrs); ++ if (rec == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ngx_memzero(rec, u.naddrs * sizeof(ngx_resolver_connection_t)); ++ ++ for (j = 0; j < u.naddrs; j++) { ++ rec[j].sockaddr = u.addrs[j].sockaddr; ++ rec[j].socklen = u.addrs[j].socklen; ++ rec[j].server = u.addrs[j].name; ++ rec[j].resolver = r; ++ } ++ ++ total++; ++ ++#if (NGX_DEBUG) ++ /* ++ * logs with level below NGX_LOG_NOTICE will not be printed ++ * in this early phase ++ */ ++ ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, ++ "parsed a resolver: \"%V\"", &u.url); ++#endif ++ ++ state = sw_nameserver; ++ } ++ ++ break; ++ ++ case sw_skip: ++ if (buf[i] == CR || buf[i] == LF) { ++ state = sw_nameserver; ++ } ++ ++ break; ++ } ++ ++ i++; ++ } ++ ++ return NGX_OK; ++} ++ ++ + ngx_resolver_t * + ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n) + { +@@ -246,6 +431,37 @@ ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n) + } + #endif + ++ if (ngx_strncmp(names[i].data, "local=", 6) == 0) { ++ ++ if (ngx_strcmp(&names[i].data[6], "on") == 0) { ++ if (ngx_resolver_read_resolv_conf(cf, r, ++ (u_char *) ++ NGX_RESOLVER_FILE_NAME, ++ sizeof(NGX_RESOLVER_FILE_NAME) ++ - 1) ++ != NGX_OK) ++ { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "unable to parse local resolver"); ++ return NULL; ++ } ++ ++ } else if (ngx_strcmp(&names[i].data[6], "off") != 0) { ++ if (ngx_resolver_read_resolv_conf(cf, r, ++ &names[i].data[6], ++ names[i].len - 6) ++ != NGX_OK) ++ { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "unable to parse local resolver"); ++ return NULL; ++ } ++ ++ } ++ ++ continue; ++ } ++ + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = names[i]; diff --git a/t/001-resolver.t b/t/001-resolver.t new file mode 100644 index 0000000..0c75ac6 --- /dev/null +++ b/t/001-resolver.t @@ -0,0 +1,345 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +#worker_connections(1014); +#master_process_enabled(1); +log_level('debug'); + +repeat_each(1); + +plan tests => repeat_each() * (blocks() * 5); + +#no_diff(); +no_long_string(); + +run_tests(); + +__DATA__ + +=== TEST 1: use system resolver +--- config + resolver local=on ipv6=off; + resolver_timeout 5s; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("openresty.org", 80) + if not ok then + ngx.say("failed to connect to openresty.org: ", err) + return + end + ngx.say("successfully connected to openresty.org") + sock:close() + } + } +--- request +GET /t +--- response_body +successfully connected to openresty.org +--- no_error_log +[error] +[crit] +--- error_log +parsed a resolver: " + + + +=== TEST 2: malformed file +--- config + resolver local=../html/resolv.conf ipv6=off; + resolver_timeout 5s; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("openresty.org", 80) + if not ok then + ngx.say("failed to connect to openresty.org: ", err) + return + end + ngx.say("successfully connected to openresty.org") + sock:close() + } + } +--- user_files eval +">>> resolv.conf +nameser 8.8.8.8" +--- request +GET /t +--- response_body +failed to connect to openresty.org: no resolver defined to resolve "openresty.org" +--- no_error_log +[error] +[crit] +parsed a resolver: " + + + +=== TEST 3: disabled +--- config + resolver local=off ipv6=off; + resolver_timeout 5s; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("openresty.org", 80) + if not ok then + ngx.say("failed to connect to openresty.org: ", err) + return + end + ngx.say("successfully connected to openresty.org") + sock:close() + } + } +--- user_files eval +">>> resolv.conf +nameser 8.8.8.8" +--- request +GET /t +--- response_body +failed to connect to openresty.org: no resolver defined to resolve "openresty.org" +--- no_error_log +[error] +[crit] +parsed a resolver: " + + + +=== TEST 4: multiple resolvers +--- config + resolver local=../html/resolv.conf ipv6=off; + resolver_timeout 5s; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("openresty.org", 80) + if not ok then + ngx.say("failed to connect to openresty.org: ", err) + return + end + ngx.say("successfully connected to openresty.org") + sock:close() + } + } +--- user_files eval +">>> resolv.conf +domain example.com +nameserver 8.8.8.8 +nameserver 8.8.4.4" +--- request +GET /t +--- response_body +successfully connected to openresty.org +--- no_error_log +[error] +[crit] +--- grep_error_log eval: qr/parsed a resolver: ".+"/ +--- grep_error_log_out +parsed a resolver: "8.8.8.8" +parsed a resolver: "8.8.4.4" + + + +=== TEST 5: CRLF +--- config + resolver local=../html/resolv.conf ipv6=off; + resolver_timeout 5s; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("openresty.org", 80) + if not ok then + ngx.say("failed to connect to openresty.org: ", err) + return + end + ngx.say("successfully connected to openresty.org") + sock:close() + } + } +--- user_files eval +">>> resolv.conf +domain example.com\r\nnameserver 8.8.8.8\r\nnameserver 8.8.4.4" +--- request +GET /t +--- response_body +successfully connected to openresty.org +--- no_error_log +[error] +[crit] +--- grep_error_log eval: qr/parsed a resolver: ".+"/ +--- grep_error_log_out +parsed a resolver: "8.8.8.8" +parsed a resolver: "8.8.4.4" + + + +=== TEST 6: CR only, with comments +--- config + resolver local=../html/resolv.conf ipv6=off; + resolver_timeout 5s; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("openresty.org", 80) + if not ok then + ngx.say("failed to connect to openresty.org: ", err) + return + end + ngx.say("successfully connected to openresty.org") + sock:close() + } + } +--- user_files eval +">>> resolv.conf +#domain example.com\rnameserver 8.8.8.8\rnameserver 8.8.4.4" +--- request +GET /t +--- response_body +successfully connected to openresty.org +--- no_error_log +[error] +[crit] +--- grep_error_log eval: qr/parsed a resolver: ".+"/ +--- grep_error_log_out +parsed a resolver: "8.8.8.8" +parsed a resolver: "8.8.4.4" + + + +=== TEST 7: spaces and tabs before and after nameserver +--- config + resolver local=../html/resolv.conf ipv6=off; + resolver_timeout 5s; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("openresty.org", 80) + if not ok then + ngx.say("failed to connect to openresty.org: ", err) + return + end + ngx.say("successfully connected to openresty.org") + sock:close() + } + } +--- user_files eval +">>> resolv.conf +domain example.com + \t \t \tnameserver \t \t 8.8.8.8 + \t \t \tnameserver\t \t8.8.4.4" +--- request +GET /t +--- response_body +successfully connected to openresty.org +--- no_error_log +[error] +[crit] +--- grep_error_log eval: qr/parsed a resolver: ".+"/ +--- grep_error_log_out +parsed a resolver: "8.8.8.8" +parsed a resolver: "8.8.4.4" + + + +=== TEST 8: MAXNS is respected (in standard Glibc it is 3) +--- config + resolver local=../html/resolv.conf ipv6=off; + resolver_timeout 5s; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("openresty.org", 80) + if not ok then + ngx.say("failed to connect to openresty.org: ", err) + return + end + ngx.say("successfully connected to openresty.org") + sock:close() + } + } +--- user_files eval +">>> resolv.conf +domain example.com +nameserver 8.8.8.8 +nameserver 8.8.4.4 +nameserver 208.67.222.222 +nameserver 208.67.220.220" +--- request +GET /t +--- response_body +successfully connected to openresty.org +--- no_error_log +[error] +[crit] +--- grep_error_log eval: qr/parsed a resolver: ".+"/ +--- grep_error_log_out +parsed a resolver: "8.8.8.8" +parsed a resolver: "8.8.4.4" +parsed a resolver: "208.67.222.222" + + + + +=== TEST 9: IPv6 is supported +--- config + resolver local=../html/resolv.conf ipv6=off; + resolver_timeout 5s; + + location = /t { + content_by_lua_block { + ngx.say("done") + } + } +--- user_files eval +">>> resolv.conf +domain example.com +nameserver 2001:4860:4860::8888 +nameserver 2001:4860:4860::8844" +--- request +GET /t +--- response_body +done +--- no_error_log +[crit] +[error] +--- grep_error_log eval: qr/parsed a resolver: ".+"/ +--- grep_error_log_out +parsed a resolver: "[2001:4860:4860::8888]" +parsed a resolver: "[2001:4860:4860::8844]" + + + +=== TEST 10: IPv6 with malformed and long address +--- config + resolver local=../html/resolv.conf ipv6=off; + resolver_timeout 5s; + + location = /t { + content_by_lua_block { + ngx.say("done") + } + } +--- user_files eval +">>> resolv.conf +domain example.com +nameserver 2001:4860:4860::8888:2001:4860:4860::8888:2001 +nameserver 2001:4860:4860::8844" +--- request +GET /t +--- response_body +done +--- no_error_log +[crit] +[error] +--- must_die +--- error_log +IPv6 resolver address is too long: "2001:4860:4860::8888:2001:4860:4860::8888:2001" +unable to parse local resolver diff --git a/util/mirror-tarballs b/util/mirror-tarballs index e603a2c..2c2d5cc 100755 --- a/util/mirror-tarballs +++ b/util/mirror-tarballs @@ -61,6 +61,10 @@ if [ "$answer" = "Y" ]; then echo "$info_txt applying the stream_ssl_preread_no_skip patch for nginx" patch -p1 < $root/patches/nginx-$main_ver-stream_ssl_preread_no_skip.patch || exit 1 echo + + echo "$info_txt applying the resolver_conf_parsing patch for nginx" + patch -p1 < $root/patches/nginx-$main_ver-resolver_conf_parsing.patch || exit 1 + echo fi answer=`$root/util/ver-ge "$main_ver" 1.5.12` diff --git a/valgrind.suppress b/valgrind.suppress new file mode 100644 index 0000000..7fff542 --- /dev/null +++ b/valgrind.suppress @@ -0,0 +1,209 @@ +{ + + Memcheck:Addr1 + fun:ngx_init_cycle + fun:ngx_master_process_cycle + fun:main +} +{ + + Memcheck:Addr4 + fun:ngx_init_cycle + fun:ngx_master_process_cycle + fun:main +} +{ + + Memcheck:Cond + fun:ngx_vslprintf + fun:ngx_snprintf + fun:ngx_sock_ntop + fun:ngx_event_accept + fun:ngx_epoll_process_events + fun:ngx_process_events_and_timers +} +{ + + Memcheck:Addr1 + fun:ngx_vslprintf + fun:ngx_snprintf + fun:ngx_sock_ntop + fun:ngx_event_accept +} +{ + + exp-sgcheck:SorG + fun:ngx_http_lua_ndk_set_var_get +} +{ + + exp-sgcheck:SorG + fun:ngx_http_variables_init_vars + fun:ngx_http_block +} +{ + + exp-sgcheck:SorG + fun:ngx_conf_parse +} +{ + + exp-sgcheck:SorG + fun:ngx_vslprintf + fun:ngx_log_error_core +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl +} +{ + + Memcheck:Cond + fun:ngx_conf_flush_files + fun:ngx_single_process_cycle +} +{ + + Memcheck:Cond + fun:memcpy + fun:ngx_vslprintf + fun:ngx_log_error_core + fun:ngx_http_charset_header_filter +} +{ + + Memcheck:Param + socketcall.setsockopt(optval) + fun:setsockopt + fun:drizzle_state_connect +} +{ + + Memcheck:Cond + fun:ngx_conf_flush_files + fun:ngx_single_process_cycle + fun:main +} +{ + + Memcheck:Leak + fun:malloc + fun:ngx_alloc + fun:ngx_event_process_init +} +{ + + Memcheck:Param + sendmsg(mmsg[0].msg_hdr) + fun:sendmmsg + fun:__libc_res_nsend +} +{ + + Memcheck:Param + sendmsg(msg.msg_iov[0]) + fun:__sendmsg_nocancel + fun:ngx_write_channel + fun:ngx_pass_open_channel + fun:ngx_start_cache_manager_processes +} +{ + + Memcheck:Cond + fun:ngx_init_cycle + fun:ngx_master_process_cycle + fun:main +} +{ + + Memcheck:Cond + fun:index + fun:expand_dynamic_string_token + fun:_dl_map_object + fun:map_doit + fun:_dl_catch_error + fun:do_preload + fun:dl_main + fun:_dl_sysdep_start + fun:_dl_start +} +{ + + Memcheck:Param + sendmsg(mmsg[0].msg_hdr) + fun:sendmmsg + fun:__libc_res_nsend + fun:__libc_res_nquery + fun:__libc_res_nquerydomain + fun:__libc_res_nsearch +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ngx_alloc + fun:ngx_set_environment + fun:ngx_single_process_cycle +} +{ + + Memcheck:Cond + obj:* +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ngx_alloc + fun:ngx_set_environment + fun:ngx_worker_process_init +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ngx_alloc + fun:ngx_create_pool + fun:main +} +{ + + Memcheck:Param + epoll_pwait(sigmask) + fun:epoll_pwait + fun:epoll_wait + fun:ngx_epoll_process_events + fun:ngx_process_events_and_timers +} +{ + + Memcheck:Param + epoll_pwait(sigmask) + fun:epoll_pwait + fun:epoll_wait + fun:ngx_epoll_test_rdhup + fun:ngx_epoll_init + fun:ngx_event_process_init +} +{ + + Memcheck:Param + epoll_pwait(sigmask) + fun:epoll_pwait + fun:ngx_epoll_process_events + fun:ngx_process_events_and_timers +} +{ + + Memcheck:Param + epoll_pwait(sigmask) + fun:epoll_pwait + fun:ngx_epoll_test_rdhup + fun:ngx_epoll_init + fun:ngx_event_process_init +}