[PATCH] Resolver: parse hosts file entries

Ruslan Ermilov ru at nginx.com
Thu Sep 14 13:31:10 UTC 2017


Hi,

On Mon, Aug 21, 2017 at 11:44:42PM -0700, Thibault Charbonnier wrote:
> In case this patch interests anyone, here is an updated version of it.
> 
> It is fully backwards compatible; as long as the new 'hostsfile' option 
> is not set, resolvers will behave as usual.
> 
> I believe it delivers on the promised features, but if it is lacking 
> anything that prevents it from being merged, I would be glad to improve 
> it (for now, the tests have been written with Test::Nginx only).
> 
> Best,
> Thibault

I did a quick glance at the previous version of your patch in July.
The current version of the patch doesn't look much different, so
below are my comments on previous version.

I personally don't like the idea of parsing /etc/hosts by nginx.
The approach choosen looks like a hack, as obviously editing the
/etc/hosts file between configuration reloads won't take any effect,
which is controversial.

Also with this approach, we would end up with some "middle" behavior
between how it's done now (when it's known that nginx does only DNS
resolving of hostnames) and if nginx was a standard utility and used
the system name resolution service which (if configured) can resolve
hostnames via /etc/hosts.  In an ideal world, nginx would prefer the
latter, but unfortunately we can't afford the blocking behavior of
the system's resolving API.

Thus, in my opinion, such an approach would raise more questions than
answers.

As to the implementation, offhand I can see two problems.  Using the
NGX_MAX_UINT32_VALUE as the maximum value won't work on systems with
32-bit signed time_t.  And the more fundamental one: the code creates
an rbtree node for each line of /etc/hosts, thus given the following
natural contents:

127.0.0.1       localhost
::1             localhost

I can't predict which one of the two addresses such a "resolver"
would return.

> # HG changeset patch
> # User Thibault Charbonnier <thibaultcha at me.com>
> # Date 1488252201 28800
> #      Mon Feb 27 19:23:21 2017 -0800
> # Node ID 558041ef1d70689ccc4c12f2487c3f75e6bbbccc
> # Parent  a2f5e25d6a283546f76435b9fc3e7e814b092bae
> Resolver: parse hosts file entries
> 
> The resolver directive can now take an optional 'hostsfile=<path>' option,
> such as:
> 
>      resolver 8.8.4.4 hostsfile=/etc/hosts;
> 
> Hosts parsed from the hosts file are considered valid forever. The behavior
> tries to be conservative, and only parses the hosts file when the option is
> provided, to enforce backwards compatibility.
> 
> Additionally, this patch makes the resolver able to handle a host file 
> in the
> absence of a nameserver, like so:
> 
>      resolver hostsfile=/etc/hosts;
> 
> The 'hostsfile' option also honors the 'ipv6' flag of the 'resolver'
> directive, as in:
> 
>      resolver 8.8.4.4 hostsfile=/etc/hosts ipv6=off;
> 
> diff -r a2f5e25d6a28 -r 558041ef1d70 src/core/ngx_resolver.c
> --- a/src/core/ngx_resolver.c    Thu Aug 10 22:21:23 2017 +0300
> +++ b/src/core/ngx_resolver.c    Mon Feb 27 19:23:21 2017 -0800
> @@ -9,11 +9,12 @@
>   #include <ngx_core.h>
>   #include <ngx_event.h>
> 
> -
> -#define NGX_RESOLVER_UDP_SIZE   4096
> -
> -#define NGX_RESOLVER_TCP_RSIZE  (2 + 65535)
> -#define NGX_RESOLVER_TCP_WSIZE  8192
> +#define NGX_RESOLVER_HOSTSFILE_BUFFER_SIZE  4096
> +
> +#define NGX_RESOLVER_UDP_SIZE               4096
> +
> +#define NGX_RESOLVER_TCP_RSIZE              (2 + 65535)
> +#define NGX_RESOLVER_TCP_WSIZE              8192
> 
> 
>   typedef struct {
> @@ -122,6 +123,8 @@
>       ngx_resolver_node_t *rn);
>   static void ngx_resolver_srv_names_handler(ngx_resolver_ctx_t *ctx);
>   static ngx_int_t ngx_resolver_cmp_srvs(const void *one, const void *two);
> +static ngx_int_t ngx_resolver_parse_hosts_file(ngx_conf_t *cf,
> +    ngx_resolver_t *r);
> 
>   #if (NGX_HAVE_INET6)
>   static void ngx_resolver_rbtree_insert_addr6_value(ngx_rbtree_node_t 
> *temp,
> @@ -130,7 +133,6 @@
>       struct in6_addr *addr, uint32_t hash);
>   #endif
> 
> -
>   ngx_resolver_t *
>   ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n)
>   {
> @@ -246,6 +248,20 @@
>           }
>   #endif
> 
> +        if (ngx_strncmp(names[i].data, "hostsfile=", 10) == 0) {
> +            r->hosts_file.len = names[i].len - 10;
> +
> +            if (r->hosts_file.len == 0) {
> +                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
> +                                   "invalid parameter: %V", &names[i]);
> +                return NULL;
> +            }
> +
> +            r->hosts_file.data = names[i].data + 10;
> +
> +            continue;
> +        }
> +
>           ngx_memzero(&u, sizeof(ngx_url_t));
> 
>           u.url = names[i];
> @@ -276,6 +292,13 @@
>           }
>       }
> 
> +    if (r->hosts_file.len > 0
> +        && ngx_resolver_parse_hosts_file(cf, r)
> +        != NGX_OK)
> +    {
> +        return NULL;
> +    }
> +
>       return r;
>   }
> 
> @@ -396,7 +419,7 @@
>           }
>       }
> 
> -    if (r->connections.nelts == 0) {
> +    if (r->connections.nelts == 0 && !r->hosts_file.len) {
>           return NGX_NO_RESOLVER;
>       }
> 
> @@ -807,6 +830,13 @@
>   #endif
> 
>           ngx_rbtree_insert(tree, &rn->node);
> +
> +        if (r->connections.nelts == 0) {
> +            ctx->quick = 1;
> +            ctx->state = NGX_RESOLVE_NXDOMAIN;
> +            ctx->handler(ctx);
> +            return NGX_OK;
> +        }
>       }
> 
>       if (ctx->service.len) {
> @@ -4652,3 +4682,281 @@
> 
>       return p1 - p2;
>   }
> +
> +
> +static ngx_int_t
> +ngx_resolver_parse_hosts_file(ngx_conf_t *cf, ngx_resolver_t *r)
> +{
> +    off_t                    file_size;
> +    u_char                   ch;
> +    u_char                  *start;
> +    size_t                   len;
> +    ssize_t                  n, size;
> +    ngx_int_t                rc;
> +    ngx_buf_t                b;
> +    ngx_fd_t                 fd;
> +    ngx_str_t                filename, s;
> +    ngx_file_t               file;
> +    in_addr_t                addr;
> +    ngx_resolver_node_t     *rn;
> +    enum {
> +        scan_line = 0,
> +        scan_skipline,
> +        scan_addr,
> +        scan_hosts,
> +        scan_name
> +    } state;
> +
> +    b.start = NULL;
> +    s.data = NULL;
> +    s.len = 0;
> +    rn = NULL;
> +    rc = NGX_OK;
> +
> +    filename = r->hosts_file;
> +
> +    fd = ngx_open_file(filename.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
> +    if (fd == NGX_INVALID_FILE) {
> +        ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
> +                      ngx_open_file_n " \"%s\" failed", filename.data);
> +        return NGX_ERROR;
> +    }
> +
> +    ngx_memzero(&file, sizeof(ngx_file_t));
> +
> +    if (ngx_fd_info(fd, &file.info) == NGX_FILE_ERROR) {
> +        ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
> +                      ngx_fd_info_n " \"%s\" failed", filename.data);
> +        goto fail;
> +    }
> +
> +    file.fd = fd;
> +    file.log = cf->log;
> +    file.name.len = filename.len;
> +    file.name.data = filename.data;
> +    file.offset = 0;
> +
> +    b.start = ngx_alloc(NGX_RESOLVER_HOSTSFILE_BUFFER_SIZE, cf->log);
> +    if (b.start == NULL) {
> +        goto fail;
> +    }
> +
> +    b.pos = b.start;
> +    b.last = b.start;
> +    b.end = b.last + NGX_RESOLVER_HOSTSFILE_BUFFER_SIZE;
> +    b.temporary = 1;
> +
> +    start = b.pos;
> +    state = scan_line;
> +    file_size = ngx_file_size(&file.info);
> +
> +    for ( ;; ) {
> +
> +        if (b.pos >= b.last) {
> +            len = b.pos - start;
> +
> +            if (len) {
> +                ngx_memmove(b.start, start, len);
> +            }
> +
> +            size = (ssize_t) (file_size - file.offset);
> +
> +            if (size > b.end - (b.start + len)) {
> +                size = b.end - (b.start + len);
> +
> +            } else if (size == 0) {
> +                goto done;
> +            }
> +
> +            n = ngx_read_file(&file, b.start + len, size, file.offset);
> +            if (n == NGX_ERROR) {
> +                ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
> +                              ngx_read_file_n, " \"%s\" failed",
> +                              filename.data);
> +                goto fail;
> +            }
> +
> +            if (n != size) {
> +                ngx_log_error(NGX_LOG_ERR, cf->log, ngx_errno,
> +                              ngx_read_file_n, " returned only %z bytes "
> +                              "instead of %z", n, size);
> +                goto fail;
> +            }
> +
> +            b.pos = b.start + len;
> +            b.last = b.pos + n;
> +            start = b.start;
> +        }
> +
> +        ch = *b.pos;
> +
> +        switch (state) {
> +
> +        case scan_line:
> +            if (ch == ' ') {
> +                break;
> +            }
> +
> +            if (ch == '#') {
> +                state = scan_skipline;
> +                break;
> +            }
> +
> +            if (ch != LF && ch != CR) {
> +                start = b.pos;
> +                state = scan_addr;
> +            }
> +
> +            break;
> +
> +        case scan_skipline:
> +            if (ch == LF || ch == CR) {
> +                state = scan_line;
> +            }
> +
> +            break;
> +
> +        case scan_addr:
> +            if (ch == LF || ch == CR) {
> +                state = scan_line;
> +                break;
> +            }
> +
> +            if (ch == ' ' || ch == '\t') {
> +                if (s.data) {
> +                    ngx_free(s.data);
> +                }
> +
> +                s.len = b.pos - start;
> +
> +                s.data = ngx_alloc(s.len, cf->log);
> +                if (s.data == NULL) {
> +                    goto fail;
> +                }
> +
> +                ngx_memcpy(s.data, start, s.len);
> +
> +                state = scan_hosts;
> +            }
> +
> +            break;
> +
> +        case scan_hosts:
> +            if (ch == LF || ch == CR) {
> +                state = scan_line;
> +                break;
> +            }
> +
> +            if (ch == ' ' || ch == '\t') {
> +                break;
> +            }
> +
> +            start = b.pos;
> +            state = scan_name;
> +            break;
> +
> +        case scan_name:
> +            if (ch == ' ' || ch == '\t' || ch == LF || ch == CR) {
> +                rn = ngx_calloc(sizeof(ngx_resolver_node_t), cf->log);
> +                if (rn == NULL) {
> +                    goto fail;
> +                }
> +
> +                rn->nlen = b.pos - start;
> +
> +                rn->name = ngx_alloc(rn->nlen, cf->log);
> +                if (rn->name == NULL) {
> +                    goto fail;
> +                }
> +
> +                ngx_memcpy(rn->name, start, rn->nlen);
> +
> +                rn->ttl = NGX_MAX_UINT32_VALUE;
> +                rn->valid = NGX_MAX_UINT32_VALUE;
> +                rn->expire = NGX_MAX_UINT32_VALUE;
> +                rn->node.key = ngx_crc32_short(rn->name, rn->nlen);
> +
> +                if (ngx_strlchr(s.data,
> +                                s.data + s.len, ':') != NULL)
> +                {
> +
> +#if (NGX_HAVE_INET6)
> +                    if (!r->ipv6
> +                        || ngx_inet6_addr(s.data, s.len,
> +                                          rn->u6.addr6.s6_addr) != NGX_OK)
> +                    {
> +#endif
> +
> +                        ngx_resolver_free_node(r, rn);
> +                        state = scan_skipline;
> +                        break;
> +
> +#if (NGX_HAVE_INET6)
> +                    }
> +
> +                    rn->naddrs6 = 1;
> +#endif
> +
> +                } else {
> +                    addr = ngx_inet_addr(s.data, s.len);
> +                    if (addr == INADDR_NONE) {
> +                        ngx_resolver_free_node(r, rn);
> +                        state = scan_skipline;
> +                        break;
> +                    }
> +
> +                    rn->naddrs = 1;
> +                    rn->u.addr = addr;
> +                }
> +
> +                ngx_log_error(NGX_LOG_NOTICE, cf->log, 0,
> +                              "host \"%*s\" will resolve to \"%V\" "
> +                              "(hosts file at \"%V\")",
> +                              rn->nlen, rn->name, &s, &filename);
> +
> +                ngx_rbtree_insert(&r->name_rbtree, &rn->node);
> +
> +                ngx_queue_insert_head(&r->name_expire_queue, &rn->queue);
> +
> +                if (ch == LF || ch == CR) {
> +                    state = scan_line;
> +                    break;
> +                }
> +
> +                state = scan_hosts;
> +            }
> +
> +            break;
> +        }
> +
> +        b.pos++;
> +    }
> +
> +fail:
> +
> +    rc = NGX_ERROR;
> +
> +done:
> +
> +    if (s.data) {
> +        ngx_free(s.data);
> +    }
> +
> +    if (b.start) {
> +        ngx_free(b.start);
> +    }
> +
> +    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
> +        ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
> +                      ngx_close_file_n, " \"%s\" failed",
> +                      filename.data);
> +        rc = NGX_ERROR;
> +    }
> +
> +    if (rc == NGX_ERROR) {
> +        return NGX_ERROR;
> +    }
> +
> +    return NGX_OK;
> +}
> +
> diff -r a2f5e25d6a28 -r 558041ef1d70 src/core/ngx_resolver.h
> --- a/src/core/ngx_resolver.h    Thu Aug 10 22:21:23 2017 +0300
> +++ b/src/core/ngx_resolver.h    Mon Feb 27 19:23:21 2017 -0800
> @@ -146,6 +146,8 @@
> 
> 
>   struct ngx_resolver_s {
> +    ngx_str_t                 hosts_file;
> +
>       /* has to be pointer because of "incomplete type" */
>       ngx_event_t              *event;
>       void                     *dummy;
> _______________________________________________
> nginx-devel mailing list
> nginx-devel at nginx.org
> http://mailman.nginx.org/mailman/listinfo/nginx-devel
> 

-- 
Ruslan Ermilov

Join us at nginx.conf, Sep. 6-8, Portland, OR
https://www.nginx.com/nginxconf


More information about the nginx-devel mailing list