diff --git a/src/anet.c b/src/anet.c index 64824a23f..369e1c641 100644 --- a/src/anet.c +++ b/src/anet.c @@ -239,7 +239,11 @@ int anetRecvTimeout(char *err, int fd, long long ms) { * * If flags is set to ANET_IP_ONLY the function only resolves hostnames * that are actually already IPv4 or IPv6 addresses. This turns the function - * into a validating / normalizing function. */ + * into a validating / normalizing function. + * + * If the flag ANET_PREFER_IPV4 is set, IPv4 is preferred over IPv6. + * If the flag ANET_PREFER_IPV6 is set, IPv6 is preferred over IPv4. + * */ int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags) { @@ -249,9 +253,20 @@ int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, memset(&hints,0,sizeof(hints)); if (flags & ANET_IP_ONLY) hints.ai_flags = AI_NUMERICHOST; hints.ai_family = AF_UNSPEC; + if (flags & ANET_PREFER_IPV4 && !(flags & ANET_PREFER_IPV6)) { + hints.ai_family = AF_INET; + } else if (flags & ANET_PREFER_IPV6 && !(flags & ANET_PREFER_IPV4)) { + hints.ai_family = AF_INET6; + } hints.ai_socktype = SOCK_STREAM; /* specify socktype to avoid dups */ - if ((rv = getaddrinfo(host, NULL, &hints, &info)) != 0) { + rv = getaddrinfo(host, NULL, &hints, &info); + if (rv != 0 && hints.ai_family != AF_UNSPEC) { + /* Try the other IP version. */ + hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET; + rv = getaddrinfo(host, NULL, &hints, &info); + } + if (rv != 0) { anetSetError(err, "%s", gai_strerror(rv)); return ANET_ERR; } diff --git a/src/anet.h b/src/anet.h index b13c14f77..08e01a4bc 100644 --- a/src/anet.h +++ b/src/anet.h @@ -40,6 +40,8 @@ /* Flags used with certain functions. */ #define ANET_NONE 0 #define ANET_IP_ONLY (1<<0) +#define ANET_PREFER_IPV4 (1<<1) +#define ANET_PREFER_IPV6 (1<<2) #if defined(__sun) || defined(_AIX) #define AF_LOCAL AF_UNIX diff --git a/src/redis-cli.c b/src/redis-cli.c index 0510b770e..930582dd9 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -275,6 +275,8 @@ static struct config { char *server_version; char *test_hint; char *test_hint_file; + int prefer_ipv4; /* Prefer IPv4 over IPv6 on DNS lookup. */ + int prefer_ipv6; /* Prefer IPv6 over IPv4 on DNS lookup. */ } config; /* User preferences. */ @@ -2768,6 +2770,10 @@ static int parseOptions(int argc, char **argv) { config.set_errcode = 1; } else if (!strcmp(argv[i],"--verbose")) { config.verbose = 1; + } else if (!strcmp(argv[i],"-4")) { + config.prefer_ipv4 = 1; + } else if (!strcmp(argv[i],"-6")) { + config.prefer_ipv6 = 1; } else if (!strcmp(argv[i],"--cluster") && !lastarg) { if (CLUSTER_MANAGER_MODE()) usage(1); char *cmd = argv[++i]; @@ -2952,6 +2958,11 @@ static int parseOptions(int argc, char **argv) { exit(1); } + if (config.prefer_ipv4 && config.prefer_ipv6) { + fprintf(stderr, "Options -4 and -6 are mutually exclusive.\n"); + exit(1); + } + return i; } @@ -3028,6 +3039,8 @@ static void usage(int err) { " -D Delimiter between responses for raw formatting (default: \\n).\n" " -c Enable cluster mode (follow -ASK and -MOVED redirections).\n" " -e Return exit error code when command execution fails.\n" +" -4 Prefer IPv4 over IPv6 on DNS lookup.\n" +" -6 Prefer IPv6 over IPv4 on DNS lookup.\n" "%s" " --raw Use raw formatting for replies (default when STDOUT is\n" " not a tty).\n" @@ -7071,7 +7084,10 @@ assign_replicas: first = node; /* Although hiredis supports connecting to a hostname, CLUSTER * MEET requires an IP address, so we do a DNS lookup here. */ - if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), ANET_NONE) + int anet_flags = ANET_NONE; + if (config.prefer_ipv4) anet_flags |= ANET_PREFER_IPV4; + if (config.prefer_ipv6) anet_flags |= ANET_PREFER_IPV6; + if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), anet_flags) == ANET_ERR) { fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip); @@ -7266,7 +7282,10 @@ static int clusterManagerCommandAddNode(int argc, char **argv) { "join the cluster.\n", ip, port); /* CLUSTER MEET requires an IP address, so we do a DNS lookup here. */ char first_ip[NET_IP_STR_LEN]; - if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), ANET_NONE) == ANET_ERR) { + int anet_flags = ANET_NONE; + if (config.prefer_ipv4) anet_flags |= ANET_PREFER_IPV4; + if (config.prefer_ipv6) anet_flags |= ANET_PREFER_IPV6; + if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), anet_flags) == ANET_ERR) { fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip); success = 0; goto cleanup; @@ -9862,6 +9881,8 @@ int main(int argc, char **argv) { config.no_auth_warning = 0; config.in_multi = 0; config.server_version = NULL; + config.prefer_ipv4 = 0; + config.prefer_ipv6 = 0; config.cluster_manager_command.name = NULL; config.cluster_manager_command.argc = 0; config.cluster_manager_command.argv = NULL; diff --git a/tests/unit/cluster/cli.tcl b/tests/unit/cluster/cli.tcl index 76e97210f..ce4629ec9 100644 --- a/tests/unit/cluster/cli.tcl +++ b/tests/unit/cluster/cli.tcl @@ -327,6 +327,8 @@ test {Migrate the last slot away from a node using redis-cli} { } } +foreach ip_or_localhost {127.0.0.1 localhost} { + # Test redis-cli --cluster create, add-node with cluster-port. # Create five nodes, three with custom cluster_port and two with default values. start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] { @@ -337,17 +339,12 @@ start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cl # The first three are used to test --cluster create. # The last two are used to test --cluster add-node - set node1_rd [redis_client 0] - set node2_rd [redis_client -1] - set node3_rd [redis_client -2] - set node4_rd [redis_client -3] - set node5_rd [redis_client -4] - test {redis-cli --cluster create with cluster-port} { - exec src/redis-cli --cluster-yes --cluster create \ - 127.0.0.1:[srv 0 port] \ - 127.0.0.1:[srv -1 port] \ - 127.0.0.1:[srv -2 port] + test "redis-cli -4 --cluster create using $ip_or_localhost with cluster-port" { + exec src/redis-cli -4 --cluster-yes --cluster create \ + $ip_or_localhost:[srv 0 port] \ + $ip_or_localhost:[srv -1 port] \ + $ip_or_localhost:[srv -2 port] wait_for_condition 1000 50 { [CI 0 cluster_state] eq {ok} && @@ -363,11 +360,11 @@ start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cl assert_equal 3 [CI 2 cluster_known_nodes] } - test {redis-cli --cluster add-node with cluster-port} { + test "redis-cli -4 --cluster add-node using $ip_or_localhost with cluster-port" { # Adding node to the cluster (without cluster-port) - exec src/redis-cli --cluster-yes --cluster add-node \ - 127.0.0.1:[srv -3 port] \ - 127.0.0.1:[srv 0 port] + exec src/redis-cli -4 --cluster-yes --cluster add-node \ + $ip_or_localhost:[srv -3 port] \ + $ip_or_localhost:[srv 0 port] wait_for_cluster_size 4 @@ -381,9 +378,9 @@ start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cl } # Adding node to the cluster (with cluster-port) - exec src/redis-cli --cluster-yes --cluster add-node \ - 127.0.0.1:[srv -4 port] \ - 127.0.0.1:[srv 0 port] + exec src/redis-cli -4 --cluster-yes --cluster add-node \ + $ip_or_localhost:[srv -4 port] \ + $ip_or_localhost:[srv 0 port] wait_for_cluster_size 5 @@ -411,6 +408,8 @@ start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cl } } +} ;# foreach ip_or_localhost + } ;# tags set ::singledb $old_singledb