]> granicus.if.org Git - pgbouncer/commitdiff
Async DNS lookup support via libevent DNS module.
authorMarko Kreen <markokr@gmail.com>
Fri, 10 Sep 2010 08:34:38 +0000 (11:34 +0300)
committerMarko Kreen <markokr@gmail.com>
Sat, 11 Sep 2010 09:06:59 +0000 (12:06 +0300)
With libevent 1.x we use evdns_base_resolve_ipv4() API,
which does not use /etc/hosts, but returns TTL.

With libevent 2.x we use evdns_getaddrinfo() API, which uses /etc/hosts
but does not return TTL.

As we need to have our own TTL handling anyway, the TTL is ignored
on libevent 1.x too, to be consistent.

New config variable 'dns_max_ttl', which sets time how long result
can be reused.

Makefile
doc/config.txt
include/bouncer.h
include/dnslookup.h [new file with mode: 0644]
include/sbuf.h
src/admin.c
src/dnslookup.c [new file with mode: 0644]
src/loader.c
src/main.c
src/objects.c
src/sbuf.c

index aac55eed9bae066fef1df1fef504ec95f6efb294..675fbf69a5157cda3c38740c4b9637b35d100712 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,10 +2,10 @@
 # sources
 SRCS = client.c loader.c objects.c pooler.c proto.c sbuf.c server.c util.c \
        admin.c stats.c takeover.c janitor.c pktbuf.c system.c main.c \
-       varcache.c
+       varcache.c dnslookup.c
 HDRS = client.h loader.h objects.h pooler.h proto.h sbuf.h server.h util.h \
        admin.h stats.h takeover.h janitor.h pktbuf.h system.h bouncer.h \
-       varcache.h iobuf.h
+       varcache.h iobuf.h dnslookup.h
 
 # data & dirs to include in tgz
 DOCS = doc/overview.txt doc/usage.txt doc/config.txt doc/todo.txt
index afd70f53fa0fae38cd73de48b124338abc9ba188..638c53298a47ac8d35b720d2a89bd664ce53a64b 100644 (file)
@@ -312,6 +312,14 @@ aspect of that is that their statistics are also forgotten.  [seconds]
 
 Default: 3600
 
+==== dns_max_ttl ====
+
+How long the DNS lookups can be cached.  If a DNS lookup returns
+several answers, pgbouncer will robin-between them in the meantime.
+Actual DNS TTL is ignored.  [seconds]
+
+Default: 15
+
 === Dangerous timeouts ===
 
 Setting following timeouts cause unexpected errors.
index e989dcac7dc8e29ac35bcff3b4577acf530444ff..ea00a552403a92aba45009c04589cc47bbcc7aa0 100644 (file)
@@ -85,6 +85,7 @@ extern int cf_sbuf_len;
 #include "sbuf.h"
 #include "pktbuf.h"
 #include "varcache.h"
+#include "dnslookup.h"
 
 #include "admin.h"
 #include "loader.h"
@@ -239,8 +240,8 @@ struct PgDatabase {
 
        PgUser *forced_user;    /* if not NULL, the user/psw is forced */
 
-       PgAddr addr;            /* address prepared for connect() */
-       char unix_socket_dir[UNIX_PATH_MAX]; /* custom unix socket dir */
+       const char *host;       /* host or unix socket name */
+       int port;
 
        int pool_size;          /* max server connections in one pool */
        int res_pool_size;      /* additional server connections in case of trouble */
@@ -341,6 +342,7 @@ extern usec_t cf_client_idle_timeout;
 extern usec_t cf_client_login_timeout;
 extern int cf_server_round_robin;
 extern int cf_disable_pqexec;
+extern usec_t cf_dns_max_ttl;
 
 extern int cf_auth_type;
 extern char *cf_auth_file;
@@ -373,6 +375,8 @@ extern ConfElem bouncer_params[];
 
 extern usec_t g_suspend_start;
 
+extern struct DNSContext *adns;
+
 static inline PgSocket * _MUSTCHECK
 pop_socket(struct StatList *slist)
 {
diff --git a/include/dnslookup.h b/include/dnslookup.h
new file mode 100644 (file)
index 0000000..270afa2
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * PgBouncer - Lightweight connection pooler for PostgreSQL.
+ *
+ * Copyright (c) 2007-2010  Marko Kreen, Skype Technologies OÜ
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <usual/base.h>
+
+struct DNSContext;
+
+typedef void (*adns_callback_f)(void *arg, int af, const void *addr);
+
+struct DNSContext *adns_create_context(void);
+void adns_reload(struct DNSContext *ctx);
+void adns_free_context(struct DNSContext *ctx);
+
+void adns_resolve(struct DNSContext *ctx, const char *name, adns_callback_f cb_func, void *arg);
+
index ca2c472806c4e9dc85ef2764d800bfbb26779935..e6144766c07f9d4be8e7c60d9438e5b299bad426 100644 (file)
@@ -61,7 +61,6 @@ typedef void (*sbuf_libevent_cb)(int, short, void *);
 struct SBuf {
        struct event ev;        /* libevent handle */
 
-       bool is_unix;           /* is it unix socket */
        uint8_t wait_type;      /* track wait state */
        uint8_t pkt_action;     /* method for handling current pkt */
 
@@ -80,7 +79,7 @@ struct SBuf {
 
 void sbuf_init(SBuf *sbuf, sbuf_cb_t proto_fn);
 bool sbuf_accept(SBuf *sbuf, int read_sock, bool is_unix)  _MUSTCHECK;
-bool sbuf_connect(SBuf *sbuf, const PgAddr *addr, const char *unix_dir, int timeout_sec)  _MUSTCHECK;
+bool sbuf_connect(SBuf *sbuf, const struct sockaddr *sa, int sa_len, int timeout_sec)  _MUSTCHECK;
 
 bool sbuf_pause(SBuf *sbuf) _MUSTCHECK;
 void sbuf_continue(SBuf *sbuf);
index 91f468210c49e12a484ea3a0f1eda49d33c0f459..fd90ed9cd11e77ea95a46f153a228ac22ffc9bfe 100644 (file)
@@ -422,7 +422,6 @@ static bool admin_show_databases(PgSocket *admin, const char *arg)
 {
        PgDatabase *db;
        struct List *item;
-       char *host;
        const char *f_user;
        PktBuf *buf;
 
@@ -438,14 +437,9 @@ static bool admin_show_databases(PgSocket *admin, const char *arg)
        statlist_for_each(item, &database_list) {
                db = container_of(item, PgDatabase, head);
 
-               if (!db->addr.is_unix) {
-                       host = inet_ntoa(db->addr.ip_addr);
-               } else
-                       host = NULL;
-
                f_user = db->forced_user ? db->forced_user->name : NULL;
                pktbuf_write_DataRow(buf, "ssissii",
-                                    db->name, host, db->addr.port,
+                                    db->name, db->host, db->port,
                                     db->dbname, f_user,
                                     db->pool_size,
                                     db->res_pool_size);
@@ -1164,8 +1158,7 @@ void admin_setup(void)
        if (!db)
                fatal("no memory for admin database");
 
-       db->addr.port = cf_listen_port;
-       db->addr.is_unix = 1;
+       db->port = cf_listen_port;
        db->pool_size = 2;
        db->admin = 1;
        if (!force_user(db, "pgbouncer", ""))
diff --git a/src/dnslookup.c b/src/dnslookup.c
new file mode 100644 (file)
index 0000000..4fb7511
--- /dev/null
@@ -0,0 +1,376 @@
+/*
+ * PgBouncer - Lightweight connection pooler for PostgreSQL.
+ *
+ * Copyright (c) 2007-2010  Marko Kreen, Skype Technologies OÜ
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "bouncer.h"
+
+/*
+ * libevent1 - returns TTL, ignores hosts file.
+ * libevent2 - does not return TTL, uses hosts file.
+ */
+
+/* do we have libevent2? */
+#ifdef EV_ET
+#define LIBEVENT2
+#endif
+
+#ifdef LIBEVENT2
+#include <event2/dns.h>
+#else
+#include <evdns.h>
+#endif
+
+
+struct UserCallback {
+       struct List node;
+       adns_callback_f cb_func;
+       void *cb_arg;
+};
+
+struct DNSRequest {
+       struct AANode node;
+       struct DNSContext *ctx;
+
+       struct List ucb_list;
+
+       const char *name;
+       int namelen;
+
+       bool done;
+
+       int res_af;
+       int res_count;
+       int res_pos;
+       void *res_list;
+       usec_t res_ttl;
+};
+
+struct DNSContext {
+       struct AATree req_tree;
+       void *edns;
+};
+
+static void deliver_info(struct DNSRequest *req);
+
+#undef log_debug
+#define log_debug log_warning
+
+
+#ifdef LIBEVENT2
+
+/*
+ * ADNS with libevent2 <event2/dns.h>
+ */
+
+static void got_result_gai(int result, struct evutil_addrinfo *res, void *arg)
+{
+       struct DNSRequest *req = arg;
+       struct evutil_addrinfo *ai;
+       int count = 0;
+       int af = 0;
+       int adrlen;
+       uint8_t *dst;
+
+       if (result != DNS_ERR_NONE) {
+               /* lookup failed */
+               log_warning("lookup failed: %s: result=%d", req->name, result);
+               goto failed;
+       }
+
+       for (ai = res; ai; ai = ai->ai_next) {
+               /* pick single family for this address */
+               if (!af) {
+                       if (ai->ai_family == AF_INET) {
+                               af = ai->ai_family;
+                               req->res_af = af;
+                               adrlen = 4;
+                       } else {
+                               continue;
+                       }
+               }
+               if (ai->ai_family != af)
+                       continue;
+               count++;
+       }
+
+       /* did not found usable entry */
+       if (!af) {
+               log_warning("dns(%s): no usable address", req->name);
+               evutil_freeaddrinfo(res);
+               goto failed;
+       }
+
+       log_debug("dns(%s): got_result_gai: count=%d, adrlen=%d", req->name, count, adrlen);
+
+       req->res_pos = 0;
+       req->done = true;
+       req->res_count = count;
+       req->res_list = malloc(adrlen * count);
+       if (!req->res_list) {
+               log_warning("req->res_list alloc failed");
+               goto failed;
+       }
+       req->res_ttl = get_cached_time() + cf_dns_max_ttl;
+
+       dst = req->res_list;
+       for (ai = res; ai; ai = ai->ai_next) {
+               struct sockaddr_in *in;
+               if (ai->ai_family != af)
+                       continue;
+               in = (void*)ai->ai_addr;
+               log_debug("dns(%s) result: %s", req->name, inet_ntoa(in->sin_addr));
+               memcpy(dst, &in->sin_addr, adrlen);
+               dst += adrlen;
+       }
+
+       deliver_info(req);
+       return;
+failed:
+       req->res_af = 0;
+       req->res_list = NULL;
+       deliver_info(req);
+}
+
+static bool impl_init(struct DNSContext *ctx)
+{
+       ctx->edns = evdns_base_new(NULL, 1);
+       if (!ctx->edns) {
+               log_warning("evdns_base_new failed");
+               return false;
+       }
+       return true;
+}
+
+static void impl_launch_query(struct DNSRequest *req)
+{
+       struct evdns_getaddrinfo_request *gai_req;
+
+       gai_req = evdns_getaddrinfo(req->ctx->edns, req->name, NULL, NULL, got_result_gai, req);
+       log_noise("dns: evdns_getaddrinfo(%s)=%p", req->name, gai_req);
+}
+
+static void impl_release(struct DNSContext *ctx)
+{
+       struct evdns_base *dns = ctx->edns;
+       evdns_base_free(dns, 0);
+}
+
+#else
+
+/*
+ * ADNS with libevent 1.x <evdns.h>
+ */
+
+static void got_result_evdns(int result, char type, int count, int ttl, void *addresses, void *arg)
+{
+       struct DNSRequest *req = arg;
+       int adrlen = 4;
+
+       log_noise("dns: got_result_evdns: type=%d cnt=%d ttl=%d", type, count, ttl);
+
+       req->done = true;
+
+       if (result != DNS_ERR_NONE || count < 1) {
+               /* lookup failed */
+               goto failed;
+       } else if (type == DNS_IPv4_A) {
+               struct in_addr *a = addresses;
+               log_debug("dns(%s): got_result_evdns: %s", req->name, inet_ntoa(*a));
+               req->res_af = AF_INET;
+               adrlen = 4;
+       } else {
+               log_warning("dns(%s): got_result_evdns unknown result: %d", req->name, type);
+               /* unknown result */
+               goto failed;
+       }
+       req->res_pos = 0;
+       req->res_count = count;
+       req->res_list = malloc(adrlen * count);
+       if (!req->res_list)
+               goto failed;
+       memcpy(req->res_list, addresses, adrlen * count);
+       req->res_ttl = get_cached_time() + cf_dns_max_ttl;
+       deliver_info(req);
+       return;
+failed:
+       req->res_af = 0;
+       req->res_list = NULL;
+       deliver_info(req);
+}
+
+static bool impl_init(struct DNSContext *ctx)
+{
+       return evdns_init() == 0;
+}
+
+static void impl_launch_query(struct DNSRequest *req)
+{
+       int err;
+
+       err = evdns_resolve_ipv4(req->name, 0, got_result_evdns, req);
+       log_noise("dns(%s): evdns_resolve_ipv4 = %d", req->name, err);
+       if (err != 0 && !req->done) {
+               /* if callback was not yet called, do it now */
+               req->done = true;
+               req->res_af = 0;
+               deliver_info(req);
+       }
+}
+
+static void impl_release(struct DNSContext *ctx)
+{
+       evdns_shutdown(0);
+}
+
+#endif
+
+/*
+ * Generic framework
+ */
+
+static void deliver_info(struct DNSRequest *req)
+{
+       struct UserCallback *ucb;
+       struct List *el;
+       const uint8_t *res = req->res_list;
+       int adrlen = 0;
+
+       if (req->res_af == AF_INET)
+               adrlen = 4;
+       else if (req->res_af == AF_INET6)
+               adrlen = 16;
+loop:
+       /* get next req */
+       el = list_pop(&req->ucb_list);
+       if (!el)
+               return;
+       ucb = container_of(el, struct UserCallback, node);
+
+       /* launch callback */
+       log_noise("dns: deliver_info(%s) type=%d pos=%d", req->name, req->res_af, req->res_pos);
+       ucb->cb_func(ucb->cb_arg, req->res_af, res + req->res_pos * adrlen);
+
+       /* round-robin between results */
+       if (req->res_count > 1) {
+               req->res_pos++;
+               if (req->res_pos >= req->res_count)
+                       req->res_pos = 0;
+       }
+
+       /* drop request */
+       list_del(&ucb->node);
+       free(ucb);
+
+       goto loop;
+}
+
+static int req_cmp(long arg, struct AANode *node)
+{
+       const char *s1 = (char *)arg;
+       struct DNSRequest *req = container_of(node, struct DNSRequest, node);
+       return strcmp(s1, req->name);
+}
+
+static void req_free(struct AANode *node, void *arg)
+{
+       struct UserCallback *ucb;
+       struct DNSRequest *req;
+       struct List *el;
+       req = container_of(node, struct DNSRequest, node);
+       while ((el = list_pop(&req->ucb_list)) != NULL) {
+               ucb = container_of(el, struct UserCallback, node);
+               free(ucb);
+       }
+       free(req->res_list);
+       free(req->name);
+       free(req);
+}
+
+struct DNSContext *adns_create_context(void)
+{
+       struct DNSContext *ctx = calloc(1, sizeof(*ctx));
+
+       aatree_init(&ctx->req_tree, req_cmp, req_free);
+       if (!impl_init(ctx)) {
+               adns_free_context(ctx);
+               return NULL;
+       }
+       return ctx;
+}
+
+void adns_free_context(struct DNSContext *ctx)
+{
+       if (ctx) {
+               impl_release(ctx);
+               aatree_destroy(&ctx->req_tree);
+               free(ctx);
+       }
+}
+
+void adns_resolve(struct DNSContext *ctx, const char *name, adns_callback_f cb_func, void *cb_arg)
+{
+       int namelen = strlen(name);
+       struct DNSRequest *req;
+       struct UserCallback *ucb;
+       struct AANode *node;
+
+       /* setup actual lookup */
+       node = aatree_search(&ctx->req_tree, (long)name);
+       if (node) {
+               req = container_of(node, struct DNSRequest, node);
+       } else {
+               log_debug("dns: new req: %s", name);
+               req = calloc(1, sizeof(*req));
+               if (!req)
+                       goto nomem;
+               req->ctx = ctx;
+               req->name = name;
+               req->namelen = namelen;
+               list_init(&req->ucb_list);
+               aatree_insert(&ctx->req_tree, (long)req->name, &req->node);
+               impl_launch_query(req);
+       }
+
+       /* remember user callback */
+       ucb = calloc(1, sizeof(*ucb));
+       if (!ucb)
+               goto nomem;
+       list_init(&ucb->node);
+       ucb->cb_func = cb_func;
+       ucb->cb_arg = cb_arg;
+       list_append(&req->ucb_list, &ucb->node);
+
+       /* if already have final result, report it */
+       if (req->done) {
+               if (req->res_ttl < get_cached_time()) {
+                       log_debug("dns: ttl over: %s", req->name);
+                       req->done = false;
+                       free(req->res_list);
+                       req->res_list = NULL;
+                       req->res_af = 0;
+
+                       impl_launch_query(req);
+               } else
+                       deliver_info(req);
+       }
+       return;
+nomem:
+       log_warning("dns(%s): req failed, no mem", name);
+       cb_func(cb_arg, 0, NULL);
+}
+
+
index 0f8ce67403e116cf68dc53e625c72cc1e9768563..d4d2f990ba382927aac7690e2b0e5af9ad8e624b 100644 (file)
@@ -187,10 +187,8 @@ void parse_database(char *name, char *connstr)
        char *datestyle = NULL;
        char *timezone = NULL;
        char *connect_query = NULL;
-       char *unix_dir = "";
        char *appname = NULL;
 
-       in_addr_t v_addr = INADDR_NONE;
        int v_port;
 
        if (strcmp(name, "*") == 0) {
@@ -238,44 +236,6 @@ void parse_database(char *name, char *connstr)
                }
        }
 
-       /* host= */
-       if (!host) {
-               /* default unix socket dir */
-               if (!*cf_unix_socket_dir) {
-                       log_error("skipping database %s because"
-                               " unix socket not configured", name);
-                       return;
-               }
-       } else if (host[0] == '/') {
-               /* custom unix socket dir */
-               unix_dir = host;
-               host = NULL;
-       } else if (host[0] >= '0' && host[0] <= '9') {
-               /* ip-address */
-               v_addr = inet_addr(host);
-               if (v_addr == INADDR_NONE) {
-                       log_error("skipping database %s because"
-                                       " of bad host: %s", name, host);
-                       return;
-               }
-       } else {
-               /* resolve host by name */
-               struct hostent *h = gethostbyname(host);
-               if (h == NULL || h->h_addr_list[0] == NULL) {
-                       log_error("%s: resolving host=%s failed: %s",
-                                 name, host, hstrerror(h_errno));
-                       return;
-               }
-               if (h->h_addrtype != AF_INET || h->h_length != 4) {
-                       log_error("%s: host=%s has unknown addr type",
-                                 name, host);
-                       return;
-               }
-
-               /* result should be already in correct endianess */
-               memcpy(&v_addr, h->h_addr_list[0], 4);
-       }
-
        /* port= */
        v_port = atoi(port);
        if (v_port == 0) {
@@ -290,6 +250,23 @@ void parse_database(char *name, char *connstr)
                return;
        }
 
+       /* host= */
+       if (host) {
+               host = strdup(host);
+               if (!host) {
+                       log_error("failed to allocate host=");
+                       return;
+               }
+       }
+       if (!host) {
+               /* default unix socket dir */
+               if (!*cf_unix_socket_dir) {
+                       log_error("skipping database %s because"
+                               " unix socket not configured", name);
+                       return;
+               }
+       }
+
        /* tag the db as alive */
        db->db_dead = 0;
        /* assuming not an autodb */
@@ -301,13 +278,11 @@ void parse_database(char *name, char *connstr)
                bool changed = false;
                if (strcmp(db->dbname, dbname) != 0)
                        changed = true;
-               else if (host && db->addr.is_unix)
+               else if (!!host != !!db->host)
                        changed = true;
-               else if (!host && !db->addr.is_unix)
+               else if (host && strcmp(host, db->host) != 0)
                        changed = true;
-               else if (host && v_addr != db->addr.ip_addr.s_addr)
-                       changed = true;
-               else if (v_port != db->addr.port)
+               else if (v_port != db->port)
                        changed = true;
                else if (username && !db->forced_user)
                        changed = true;
@@ -315,8 +290,6 @@ void parse_database(char *name, char *connstr)
                        changed = true;
                else if (!username && db->forced_user)
                        changed = true;
-               else if (strcmp(db->unix_socket_dir, unix_dir) != 0)
-                       changed = true;
                else if ((db->connect_query && !connect_query)
                         || (!db->connect_query && connect_query)
                         || (connect_query && strcmp(connect_query, db->connect_query) != 0))
@@ -329,13 +302,11 @@ void parse_database(char *name, char *connstr)
        /* if pool_size < 0 it will be set later */
        db->pool_size = pool_size;
        db->res_pool_size = res_pool_size;
-       db->addr.port = v_port;
-       db->addr.ip_addr.s_addr = v_addr;
-       db->addr.is_unix = host ? 0 : 1;
-       safe_strcpy(db->unix_socket_dir, unix_dir, sizeof(db->unix_socket_dir));
 
-       if (host)
-               log_debug("%s: host=%s/%s", name, host, inet_ntoa(db->addr.ip_addr));
+       if (db->host)
+               free(db->host);
+       db->host = host;
+       db->port = v_port;
 
        /* assign connect_query */
        set_connect_query(db, connect_query);
index 4e10a092ff28e0a484db99acfbf433709df155f9..59b2ee4b1c9b38199d3ac7ab3b3c55e8d62a7b16 100644 (file)
@@ -52,6 +52,9 @@ static void usage(int err, char *exe)
        exit(err);
 }
 
+/* async dns handler */
+struct DNSContext *adns;
+
 /*
  * configuration storage
  */
@@ -103,6 +106,7 @@ char *cf_server_check_query = "select 1";
 usec_t cf_server_check_delay = 30 * USEC;
 int cf_server_round_robin = 0;
 int cf_disable_pqexec = 0;
+usec_t cf_dns_max_ttl = 15 * USEC;
 
 char *cf_ignore_startup_params = "";
 
@@ -180,6 +184,7 @@ ConfElem bouncer_params[] = {
 {"suspend_timeout",    true, CF_TIME, &cf_suspend_timeout},
 {"ignore_startup_parameters", true, CF_STR, &cf_ignore_startup_params},
 {"disable_pqexec",     false, CF_INT, &cf_disable_pqexec},
+{"dns_max_ttl",                true, CF_TIME, &cf_dns_max_ttl},
 
 {"pkt_buf",            false, CF_INT, &cf_sbuf_len},
 {"sbuf_loopcnt",       true, CF_INT, &cf_sbuf_loopcnt},
@@ -649,6 +654,15 @@ static void takeover_part1(void)
        event_base_free(evtmp);
 }
 
+static void dns_setup(void)
+{
+       if (adns)
+               return;
+       adns = adns_create_context();
+       if (!adns)
+               fatal_perror("dns setup failed");
+}
+
 /* boot everything */
 int main(int argc, char *argv[])
 {
@@ -745,6 +759,7 @@ int main(int argc, char *argv[])
        srandom(time(NULL) ^ getpid());
        if (!event_init())
                fatal("event_init() failed");
+       dns_setup();
        signal_setup();
        janitor_setup();
        stats_setup();
index 743ca57fa5b9938cd12bd6cee66cb92b360b986c..de005ad0eb30f4c30e614991a4d7886e2d30c887 100644 (file)
@@ -807,13 +807,104 @@ void disconnect_client(PgSocket *client, bool notify, const char *reason, ...)
                log_noise("sbuf_close failed, retry later");
 }
 
+/*
+ * Connection creation utilities
+ */
+
+static void connect_server(struct PgSocket *server, struct sockaddr *sa, int salen)
+{
+       bool res;
+
+       /* fill remote_addr */
+       memset(&server->remote_addr, 0, sizeof(server->remote_addr));
+       if (sa->sa_family == AF_UNIX) {
+               server->remote_addr.port = server->pool->db->port;
+               server->remote_addr.is_unix = true;
+       } else if (sa->sa_family == AF_INET) {
+               struct sockaddr_in *in = (struct sockaddr_in *)sa;
+               server->remote_addr.port = in->sin_port;
+               server->remote_addr.ip_addr = in->sin_addr;
+       }
+
+       /* start connecting */
+       res = sbuf_connect(&server->sbuf, sa, salen,
+                          cf_server_connect_timeout / USEC);
+       if (!res)
+               log_noise("failed to launch new connection");
+}
+
+static void dns_callback(void *arg, int af, const void *addr)
+{
+       struct PgSocket *server = arg;
+       struct PgDatabase *db = server->pool->db;
+       struct sockaddr_in sa_in;
+       struct sockaddr *sa;
+       int salen;
+
+       if (af == AF_INET) {
+               char buf[64];
+               memset(&sa_in, 0, sizeof(sa_in));
+               sa_in.sin_family = af;
+               sa_in.sin_addr.s_addr = *(in_addr_t *)addr;
+               sa_in.sin_port = htons(db->port);
+               sa = (struct sockaddr *)&sa_in;
+               salen = sizeof(sa_in);
+               slog_debug(server, "dns_callback: inet4: %s",
+                          sa2str(sa, buf, sizeof(buf)));
+       } else if (!af) {
+               disconnect_server(server, true, "server dns lookup failed");
+               return;
+       } else {
+               disconnect_server(server, true, "unknown dns type");
+               return;
+       }
+
+       connect_server(server, sa, salen);
+}
+
+static void dns_connect(struct PgSocket *server)
+{
+       struct sockaddr_un sa_un;
+       struct sockaddr_in sa_in;
+       struct sockaddr *sa;
+       int salen;
+       struct PgDatabase *db = server->pool->db;
+       const char *host = db->host;
+       const char *unix_dir;
+       int sa_len;
+
+       if (!host || host[0] == '/') {
+               slog_noise(server, "unix socket: %s", sa_un.sun_path);
+               memset(&sa_un, 0, sizeof(sa_un));
+               sa_un.sun_family = AF_UNIX;
+               unix_dir = host ? host : cf_unix_socket_dir;
+               snprintf(sa_un.sun_path, sizeof(sa_un.sun_path),
+                        "%s/.s.PGSQL.%d", unix_dir, db->port);
+               sa = (struct sockaddr *)&sa_un;
+               sa_len = sizeof(sa_un);
+       } else if (host[0] >= '0' && host[0] <= '9') {
+               slog_noise(server, "inet socket: %s", db->host);
+               memset(&sa_in, 0, sizeof(sa_in));
+               sa_in.sin_family = AF_INET;
+               sa_in.sin_addr.s_addr = inet_addr(db->host);
+               sa_in.sin_port = htons(db->port);
+               sa = (struct sockaddr *)&sa_in;
+               sa_len = sizeof(sa_in);
+       } else {
+               slog_noise(server, "dns socket: %s", db->host);
+               /* launch dns lookup */
+               adns_resolve(adns, db->host, dns_callback, server);
+               return;
+       }
+
+       connect_server(server, sa, salen);
+}
+
 /* the pool needs new connection, if possible */
 void launch_new_connection(PgPool *pool)
 {
        PgSocket *server;
        int total;
-       const char *unix_dir = cf_unix_socket_dir;
-       bool res;
 
        /* allow only small number of connection attempts at a time */
        if (!statlist_empty(&pool->new_server_list)) {
@@ -860,7 +951,6 @@ allow_new:
        /* initialize it */
        server->pool = pool;
        server->auth_user = server->pool->user;
-       server->remote_addr = server->pool->db->addr;
        server->connect_time = get_cached_time();
        pool->last_connect_time = get_cached_time();
        change_server_state(server, SV_LOGIN);
@@ -868,15 +958,7 @@ allow_new:
        if (cf_log_connections)
                slog_info(server, "new connection to server");
 
-       /* override socket location if requested */
-       if (server->pool->db->unix_socket_dir[0])
-               unix_dir = server->pool->db->unix_socket_dir;
-
-       /* start connecting */
-       res = sbuf_connect(&server->sbuf, &server->remote_addr, unix_dir,
-                          cf_server_connect_timeout / USEC);
-       if (!res)
-               log_noise("failed to launch new connection");
+       dns_connect(server);
 }
 
 /* new client connection attempt */
index 370ceabfa4b891fe372b0022954fa93dbad98570..6f950238730d6c7964548c36f27f3ad8e0e3008d 100644 (file)
@@ -88,7 +88,6 @@ bool sbuf_accept(SBuf *sbuf, int sock, bool is_unix)
 
        tune_socket(sock, is_unix);
        sbuf->sock = sock;
-       sbuf->is_unix = is_unix;
 
        if (!cf_reboot) {
                res = sbuf_wait_for_data(sbuf);
@@ -107,55 +106,32 @@ bool sbuf_accept(SBuf *sbuf, int sock, bool is_unix)
 }
 
 /* need to connect() to get a socket */
-bool sbuf_connect(SBuf *sbuf, const PgAddr *addr, const char *unix_dir, int timeout_sec)
+bool sbuf_connect(SBuf *sbuf, const struct sockaddr *sa, int sa_len, int timeout_sec)
 {
-       int res, sock, domain;
-       struct sockaddr_in sa_in;
-       struct sockaddr_un sa_un;
-       struct sockaddr *sa;
-       socklen_t len;
+       int res, sock;
        struct timeval timeout;
+       bool is_unix = sa->sa_family == AF_UNIX;
 
        Assert(iobuf_empty(sbuf->io) && sbuf->sock == 0);
        AssertSanity(sbuf);
 
-       /* prepare sockaddr */
-       if (addr->is_unix) {
-               sa = (void*)&sa_un;
-               len = sizeof(sa_un);
-               memset(sa, 0, len);
-               sa_un.sun_family = AF_UNIX;
-               snprintf(sa_un.sun_path, sizeof(sa_un.sun_path),
-                        "%s/.s.PGSQL.%d", unix_dir, addr->port);
-               domain = AF_UNIX;
-       } else {
-               sa = (void*)&sa_in;
-               len = sizeof(sa_in);
-               memset(sa, 0, len);
-               sa_in.sin_family = AF_INET;
-               sa_in.sin_addr = addr->ip_addr;
-               sa_in.sin_port = htons(addr->port);
-               domain = AF_INET;
-       }
-
        /*
         * common stuff
         */
-       sock = socket(domain, SOCK_STREAM, 0);
+       sock = socket(sa->sa_family, SOCK_STREAM, 0);
        if (sock < 0)
                /* probably fd limit */
                goto failed;
 
-       tune_socket(sock, addr->is_unix);
+       tune_socket(sock, is_unix);
 
-       sbuf->is_unix = addr->is_unix;
        sbuf->sock = sock;
 
        timeout.tv_sec = timeout_sec;
        timeout.tv_usec = 0;
 
        /* launch connection */
-       res = safe_connect(sock, sa, len);
+       res = safe_connect(sock, sa, sa_len);
        if (res == 0) {
                /* unix socket gives connection immidiately */
                sbuf_connect_cb(sock, EV_WRITE, sbuf);