diff --git a/hiredis.c b/hiredis.c index 1fd8570ac..54a10ffba 100644 --- a/hiredis.c +++ b/hiredis.c @@ -809,6 +809,12 @@ redisContext *redisConnectWithOptions(const redisOptions *options) { if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) { c->flags |= REDIS_NO_AUTO_FREE_REPLIES; } + if (options->options & REDIS_OPT_PREFER_IPV4) { + c->flags |= REDIS_PREFER_IPV4; + } + if (options->options & REDIS_OPT_PREFER_IPV6) { + c->flags |= REDIS_PREFER_IPV6; + } /* Set any user supplied RESP3 PUSH handler or use freeReplyObject * as a default unless specifically flagged that we don't want one. */ diff --git a/hiredis.h b/hiredis.h index 7e2a84d1c..1cbdde4aa 100644 --- a/hiredis.h +++ b/hiredis.h @@ -92,6 +92,11 @@ typedef long long ssize_t; /* Flag that indicates the user does not want replies to be automatically freed */ #define REDIS_NO_AUTO_FREE_REPLIES 0x400 +/* Flags to prefer IPv6 or IPv4 when doing DNS lookup. (If both are set, + * AF_UNSPEC is used.) */ +#define REDIS_PREFER_IPV4 0x800 +#define REDIS_PREFER_IPV6 0x1000 + #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and @@ -149,6 +154,8 @@ struct redisSsl; #define REDIS_OPT_NONBLOCK 0x01 #define REDIS_OPT_REUSEADDR 0x02 +#define REDIS_OPT_PREFER_IPV4 0x04 +#define REDIS_OPT_PREFER_IPV6 0x08 /** * Don't automatically free the async object on a connection failure, diff --git a/net.c b/net.c index 8378c289b..8cd3d7159 100644 --- a/net.c +++ b/net.c @@ -439,17 +439,25 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; - /* Try with IPv6 if no IPv4 address was found. We do it in this order since - * in a Redis client you can't afford to test if you have IPv6 connectivity - * as this would add latency to every connect. Otherwise a more sensible - * route could be: Use IPv6 if both addresses are available and there is IPv6 - * connectivity. */ - if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { - hints.ai_family = AF_INET6; - if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { - __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); - return REDIS_ERR; - } + /* DNS lookup. To use dual stack, set both flags to prefer both IPv4 and + * IPv6. By default, for historical reasons, we try IPv4 first and then we + * try IPv6 only if no IPv4 address was found. */ + if (c->flags & REDIS_PREFER_IPV6 && c->flags & REDIS_PREFER_IPV4) + hints.ai_family = AF_UNSPEC; + else if (c->flags & REDIS_PREFER_IPV6) + hints.ai_family = AF_INET6; + else + hints.ai_family = AF_INET; + + rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo); + if (rv != 0 && hints.ai_family != AF_UNSPEC) { + /* Try again with the other IP version. */ + hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET; + rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo); + } + if (rv != 0) { + __redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv)); + return REDIS_ERR; } for (p = servinfo; p != NULL; p = p->ai_next) { addrretry: