Repository: redis/redis

Issue:
[NEW] redis-cli --cluster create HOSTNAME:port
**The problem/use-case that the feature addresses**

When hostnames are used, redis-cli is not capable of setting up a cluster using only hostnames. Originally reported in [this comment](https://github.com/redis/redis/pull/9530#issuecomment-1068707524) by @liuchong.

**Description of the feature**

Support `CLUSTER MEET hostname port` (do DNS lookup in Redis)

**Alternatives you've considered**

Let redis-cli do the DNS lookup.

**Additional information**

Thanks to hiredis, redis-cli can already connect using hostnames, but that's implicitly handled by hiredis.

DNS lookup already implemented in `anetResolve()` which is used by Sentinel. It would only take a few lines to add it, but tests might require hostnames with DNS records. This is regardless of if we add it to redis-cli or Redis itself.

In #9530 DNS lookup in Redis is listed as "Out of scope". A benefit of doing the DNS lookup in Redis (rather than in redis-cli) is that we can implement TLS SNI/SAN validation for the cluster bus later. That might require more changes though since the hostnames are not currently gossiped.

@madolson Would you prefer DNS lookup to happen in `redis-cli` or in CLUSTER MEET?


Discussion:
Related: #3484
@zuiderkwast Strong preference for the DNS lookup to happen in the redis-cli. The hostname should just be a way for clients to connect, since we don't want a DNS dependency inside Redis. In some scenarios, it's possible the DNS isn't even resolvable from the redis nodes.
@madolson In other scenarios, it's also possible that clients will resolve hostnames to IPs that are different than the ones Redis will resolve to. I think it's OK to keep it on the client side for now, but there's an unresolved issue here (no pun intended).
@yossigo I don't think that is a concern directly. I think you should be calling this orchestration operation while in the same network as the server. Using hostnames here is implicitly risky, since we will resolve to an IP address and then never pay attention to the hostname again.

Patch:
diff --git a/src/redis-cli.c b/src/redis-cli.c
index e06e6af8a27..f3f46f3484b 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -156,6 +156,9 @@
 #define CC_FORCE (1<<0)         /* Re-connect if already connected. */
 #define CC_QUIET (1<<1)         /* Don't log connecting errors. */
 
+/* DNS lookup */
+#define NET_IP_STR_LEN 46       /* INET6_ADDRSTRLEN is 46 */
+
 /* --latency-dist palettes. */
 int spectrum_palette_color_size = 19;
 int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196};
@@ -6306,16 +6309,26 @@ static int clusterManagerCommandCreate(int argc, char **argv) {
         clusterManagerLogInfo(">>> Sending CLUSTER MEET messages to join "
                               "the cluster\n");
         clusterManagerNode *first = NULL;
+        char first_ip[NET_IP_STR_LEN]; /* first->ip may be a hostname */
         listRewind(cluster_manager.nodes, &li);
         while ((ln = listNext(&li)) != NULL) {
             clusterManagerNode *node = ln->value;
             if (first == NULL) {
                 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)
+                    == ANET_ERR)
+                {
+                    fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip);
+                    success = 0;
+                    goto cleanup;
+                }
                 continue;
             }
             redisReply *reply = NULL;
             reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d",
-                                            first->ip, first->port);
+                                            first_ip, first->port);
             int is_err = 0;
             if (reply != NULL) {
                 if ((is_err = reply->type == REDIS_REPLY_ERROR))
@@ -6487,8 +6500,15 @@ static int clusterManagerCommandAddNode(int argc, char **argv) {
     // Send CLUSTER MEET command to the new node
     clusterManagerLogInfo(">>> Send CLUSTER MEET to node %s:%d to make it "
                           "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) {
+        fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip);
+        success = 0;
+        goto cleanup;
+    }
     reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d",
-                                    first->ip, first->port);
+                                    first_ip, first->port);
     if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
         goto cleanup;