This patch adds the dynamic DNS resolution to the servers specified in the upstream block via the "resolve" keyword. The syntax is backward compatible with the official documentation for this functionality and should allow one to switch between the community and commercial versions of nginx with no config changes. To define the upstream server with dynamic DNS resolution simply add the resolve keyword as a parameter: === upstream backend { server backend.domain.tld.:9000 resolve; } === diff -puNr nginx-1.10.1.orig/src/http/ngx_http_upstream.c nginx-1.10.1/src/http/ngx_http_upstream.c --- nginx-1.10.1.orig/src/http/ngx_http_upstream.c 2016-05-31 13:47:02.000000000 +0000 +++ nginx-1.10.1/src/http/ngx_http_upstream.c 2016-06-12 02:15:59.008000000 +0000 @@ -5418,7 +5418,8 @@ ngx_http_upstream(ngx_conf_t *cf, ngx_co |NGX_HTTP_UPSTREAM_MAX_FAILS |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT |NGX_HTTP_UPSTREAM_DOWN - |NGX_HTTP_UPSTREAM_BACKUP); + |NGX_HTTP_UPSTREAM_BACKUP + |NGX_HTTP_UPSTREAM_RESOLVE); if (uscf == NULL) { return NGX_CONF_ERROR; } @@ -5605,6 +5606,17 @@ ngx_http_upstream_server(ngx_conf_t *cf, continue; } + if (ngx_strcmp(value[i].data, "resolve") == 0) { + + if (!(uscf->flags & NGX_HTTP_UPSTREAM_RESOLVE)) { + goto not_supported; + } + + us->resolve = 1; + + continue; + } + goto invalid; } @@ -5629,6 +5641,28 @@ ngx_http_upstream_server(ngx_conf_t *cf, us->max_fails = max_fails; us->fail_timeout = fail_timeout; + us->host = u.host; + /* For some reason nginx'es developers are keeping ports in the url.host field, we don't need this */ + if ( +#if (NGX_HAVE_INET6) + (u.family == AF_INET6) || +#endif + (u.family == AF_INET)) { + u_char *last, *port; + last = us->host.data + u.host.len; + port = ngx_strlchr(us->host.data, last, ':'); + us->host.len = (port ? port : last) - us->host.data; + + /* The following is a change in the behavior: we replace ':' with the NULL character if there + was the ':' character in the host string. This may potentially break logic that expects + the host string to contain both the hostname and the port, but quick checks show that it is + unlikely and that the current nginx'es behaviour is likely a bug. In case this indeed + affects other parts of nginx, we would need to copy the string, but this means additional + memory will be spent. + */ + us->host.data[us->host.len] = '\0'; + } + return NGX_CONF_OK; invalid: diff -puNr nginx-1.10.1.orig/src/http/ngx_http_upstream.h nginx-1.10.1/src/http/ngx_http_upstream.h --- nginx-1.10.1.orig/src/http/ngx_http_upstream.h 2016-05-31 13:47:02.000000000 +0000 +++ nginx-1.10.1/src/http/ngx_http_upstream.h 2016-06-12 02:17:49.518000000 +0000 @@ -91,6 +91,7 @@ typedef struct { typedef struct { ngx_str_t name; + ngx_str_t host; ngx_addr_t *addrs; ngx_uint_t naddrs; ngx_uint_t weight; @@ -99,6 +100,7 @@ typedef struct { unsigned down:1; unsigned backup:1; + unsigned resolve:1; } ngx_http_upstream_server_t; @@ -108,6 +110,7 @@ typedef struct { #define NGX_HTTP_UPSTREAM_FAIL_TIMEOUT 0x0008 #define NGX_HTTP_UPSTREAM_DOWN 0x0010 #define NGX_HTTP_UPSTREAM_BACKUP 0x0020 +#define NGX_HTTP_UPSTREAM_RESOLVE 0x0040 struct ngx_http_upstream_srv_conf_s { diff -puNr nginx-1.10.1.orig/src/http/ngx_http_upstream_round_robin.c nginx-1.10.1/src/http/ngx_http_upstream_round_robin.c --- nginx-1.10.1.orig/src/http/ngx_http_upstream_round_robin.c 2016-05-31 13:47:02.000000000 +0000 +++ nginx-1.10.1/src/http/ngx_http_upstream_round_robin.c 2016-06-12 02:35:09.700000000 +0000 @@ -50,6 +50,12 @@ ngx_http_upstream_init_round_robin(ngx_c continue; } + if (server[i].resolve) { + n++; + w += server[i].weight; + continue; + } + n += server[i].naddrs; w += server[i].naddrs * server[i].weight; } @@ -86,9 +92,15 @@ ngx_http_upstream_init_round_robin(ngx_c } for (j = 0; j < server[i].naddrs; j++) { + if (server[i].resolve) { + peer[n].resolve = 1; + peer[n].name = server[i].host; + } else { + peer[n].resolve = 0; + peer[n].name = server[i].addrs[j].name; + } peer[n].sockaddr = server[i].addrs[j].sockaddr; peer[n].socklen = server[i].addrs[j].socklen; - peer[n].name = server[i].addrs[j].name; peer[n].weight = server[i].weight; peer[n].effective_weight = server[i].weight; peer[n].current_weight = 0; @@ -115,6 +127,12 @@ ngx_http_upstream_init_round_robin(ngx_c continue; } + if (server[i].resolve) { + n++; + w += server[i].weight; + continue; + } + n += server[i].naddrs; w += server[i].naddrs * server[i].weight; } @@ -149,9 +167,15 @@ ngx_http_upstream_init_round_robin(ngx_c } for (j = 0; j < server[i].naddrs; j++) { + if (server[i].resolve) { + peer[n].resolve = 1; + peer[n].name = server[i].host; + } else { + peer[n].resolve = 0; + peer[n].name = server[i].addrs[j].name; + } peer[n].sockaddr = server[i].addrs[j].sockaddr; peer[n].socklen = server[i].addrs[j].socklen; - peer[n].name = server[i].addrs[j].name; peer[n].weight = server[i].weight; peer[n].effective_weight = server[i].weight; peer[n].current_weight = 0; @@ -243,6 +267,7 @@ ngx_http_upstream_init_round_robin_peer( { ngx_uint_t n; ngx_http_upstream_rr_peer_data_t *rrp; + ngx_http_core_loc_conf_t *clcf; rrp = r->upstream->peer.data; @@ -258,6 +283,14 @@ ngx_http_upstream_init_round_robin_peer( rrp->peers = us->peer.data; rrp->current = NULL; + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + if (!clcf->resolver) { + /* No resolver context => cannot resolve anything */ + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "[rr resolver] no resolver context, upstream servers requiring resolver will be skipped"); + } + rrp->resolver = clcf->resolver ? clcf->resolver : NULL; + rrp->resolver_timeout = clcf->resolver_timeout; + n = rrp->peers->number; if (rrp->peers->next && rrp->peers->next->number > n) { @@ -415,6 +448,93 @@ ngx_http_upstream_create_round_robin_pee } +static void +ngx_http_upstream_resolve_peer_handler(ngx_resolver_ctx_t *ctx) +{ + ngx_peer_connection_t *pc = ctx->data; + in_port_t port; + + if (!pc || !pc->connection || pc->cached) { + goto failed; + } + + if (ctx->state) { + ngx_log_error(NGX_LOG_ERR, pc->log, 0, + "%V could not be resolved (%i: %s)", + &ctx->name, ctx->state, + ngx_resolver_strerror(ctx->state)); + goto failed; + } + + switch (pc->sockaddr->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + port = ((struct sockaddr_in6 *)pc->sockaddr)->sin6_port; + break; +#endif + default: /* AF_INET */ + port = ((struct sockaddr_in *)pc->sockaddr)->sin_port; + } + + ngx_memcpy(pc->sockaddr, ctx->addrs[0].sockaddr, ctx->addrs[0].socklen); + pc->socklen = ctx->addrs[0].socklen; + + switch (pc->sockaddr->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + ((struct sockaddr_in6 *)pc->sockaddr)->sin6_port = port; + break; +#endif + default: /* AF_INET */ + ((struct sockaddr_in *)pc->sockaddr)->sin_port = port; + } + +failed: + + ngx_resolve_name_done(ctx); +} + + +ngx_int_t +ngx_http_upstream_resolve_round_robin_peer(ngx_peer_connection_t *pc, void *data) +{ + ngx_http_upstream_rr_peer_data_t *rrp = data; + ngx_resolver_ctx_t *ctx, temp; + + if (!rrp->resolver) { + /* No resolver context => cannot resolve anything */ + ngx_log_error(NGX_LOG_ERR, pc->log, 0, "[rr resolver] no resolver context present (attempted to resolve:: %V)", pc->name); + return NGX_ERROR; + } + + temp.name = *pc->name; /* if hostname is an IP address it will be quick */ + ctx = ngx_resolve_start(rrp->resolver, &temp); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, rrp->resolver->log, 0, "failed to start resolver for %V", pc->name); + return NGX_ERROR; + } + + if (ctx == NGX_NO_RESOLVER) { + ngx_log_error(NGX_LOG_ERR, rrp->resolver->log, 0, + "no resolver defined to resolve %V", pc->name); + + return NGX_ERROR; + } + + ctx->name = *pc->name; + ctx->handler = ngx_http_upstream_resolve_peer_handler; + ctx->data = pc; + ctx->timeout = rrp->resolver_timeout; + + if (ngx_resolve_name(ctx) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, rrp->resolver->log, 0, "resolve_name() failed for %V", pc->name); + return NGX_ERROR; + } + + return NGX_OK; +} + + ngx_int_t ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data) { @@ -466,6 +586,14 @@ ngx_http_upstream_get_round_robin_peer(n ngx_http_upstream_rr_peers_unlock(peers); + if (peer->resolve) { + rc = ngx_http_upstream_resolve_round_robin_peer(pc, data); + if (rc == NGX_ERROR) { + goto failed; + } + return rc; + } + return NGX_OK; failed: diff -puNr nginx-1.10.1.orig/src/http/ngx_http_upstream_round_robin.h nginx-1.10.1/src/http/ngx_http_upstream_round_robin.h --- nginx-1.10.1.orig/src/http/ngx_http_upstream_round_robin.h 2016-05-31 13:47:02.000000000 +0000 +++ nginx-1.10.1/src/http/ngx_http_upstream_round_robin.h 2016-06-12 02:36:29.139000000 +0000 @@ -36,6 +36,7 @@ struct ngx_http_upstream_rr_peer_s { time_t fail_timeout; ngx_uint_t down; /* unsigned down:1; */ + ngx_uint_t resolve; /* unsigned resolve:1; */ #if (NGX_HTTP_SSL) void *ssl_session; @@ -122,6 +123,8 @@ typedef struct { ngx_http_upstream_rr_peers_t *peers; ngx_http_upstream_rr_peer_t *current; uintptr_t *tried; + ngx_resolver_t *resolver; /* resolver */ + ngx_msec_t resolver_timeout; /* resolver_timeout */ uintptr_t data; } ngx_http_upstream_rr_peer_data_t;