redis-cli is not using the previously used host address to connect while getting a null/None response for the host during server redirects.

When the Redis cluster is configured with cluster-preferred-endpoint-type unknown-endpoint, it indicates that the server doesn't know how clients can reach it during redirects. This is made clear to the client from the null response on the host field during redirects from the server. In this case, the server is expecting the client to reach out on the same endpoint it used for making the last request, but use the new port provided in the response.

This does not happen with redis-cli. It tries to connect with a null hostname during redirects which is not the expected behavior from the server.

Example: All cluster member hosts are accessible via the same DNS/IP address but on different ports. Preferred endpoint type set to unknown-endpoint - cluster-preferred-endpoint-type unknown-endpoint.

test1:

redis.carrie.test:6379> set suresh-key1 1
-> Redirected to slot [12977] located at :6382
Could not connect to Redis at :6382: Name or service not known
not connected>

Expectation: The client should try connecting to redis.carrie.test:6382 instead of :6382

test2:

18.60.156.81:6379> set b 1
-> Redirected to slot [3300] located at :6380
Could not connect to Redis at :6380: Name or service not known
not connected>

Expectation: The client should try connecting to 18.60.156.81:6380 instead of :6380

To reproduce:

  • Set up a Redis cluster on a single host or behind a load balancer with TCP port mapping. Basically, each cluster member is configured on a different port but should be accessible via the same DNS/hostname/IP.
  • The cluster should have the preferred endpoint set to unknown-endpoint - cluster-preferred-endpoint-type unknown-endpoint
  • Connect via redis-cli and check to see how null hostname/IP redirects from the cluster are interpreted by the client.

Expected behavior:

As shown in the example, when a server redirects a client with :new port, the client should connect using the same hostname/IP/endpoint it used previously to connect to the server but with the new port value indicated in the redirect.

Additional information: Issue observed on the following redis-server and redis-cli versions: 7.0.9 7.0.11

Comment From: zuiderkwast

Thanks for the report! unknown-endpoint is a relatively new feature in Redis. It seems it has not been implemented in redis-cli yet.

I think it's not very hard to fix it. Also easy to test. We have a test suite for redis-cli where we can write a test just like you described it.

Comment From: MakDon

Thanks for the report! unknown-endpoint is a relatively new feature in Redis. It seems it has not been implemented in redis-cli yet.

I think it's not very hard to fix it. Also easy to test. We have a test suite for redis-cli where we can write a test just like you described it.

Could we check the hostip here, and do not update the hostip if sdsnew(p+1) is empty ?

    if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
        (!strncmp(reply->str,"MOVED ",6) || !strncmp(reply->str,"ASK ",4)))
    {
        char *p = reply->str, *s;
        int slot;

        output = 0;
        /* Comments show the position of the pointer as:
         *
         * [S] for pointer 's'
         * [P] for pointer 'p'
         */
        s = strchr(p,' ');      /* MOVED[S]3999 127.0.0.1:6381 */
        p = strchr(s+1,' ');    /* MOVED[S]3999[P]127.0.0.1:6381 */
        *p = '\0';
        slot = atoi(s+1);
        s = strrchr(p+1,':');    /* MOVED 3999[P]127.0.0.1[S]6381 */
        *s = '\0';
        sdsfree(config.conn_info.hostip);

        /*
         * Could we fix here: 
         * if p + 1 is empty string, then we do not update config.conn_info.hostip?
         */
        char *hostip = sdsnew(p+1);
        if (sdslen(hostip) != 0) {
            config.conn_info.hostip = hostip;
        }
        config.conn_info.hostport = atoi(s+1);
        if (config.interactive)
            printf("-> Redirected to slot [%d] located at %s:%d\n",
                slot, config.conn_info.hostip, config.conn_info.hostport);
        config.cluster_reissue_command = 1;
        if (!strncmp(reply->str,"ASK ",4)) {
            config.cluster_send_asking = 1;
        }
        cliRefreshPrompt();
    }

Comment From: zuiderkwast

@MakDon yes it makes sense.

I would check if it's empty before sdsnew to avoid creating the sds string if it is not needed. We can check if p+1 == s. It means the host is empty, right? If you open a PR we can do code review easier.

Comment From: MakDon

@MakDon yes it makes sense.

I would check if it's empty before sdsnew to avoid creating the sds string if it is not needed. We can check if p+1 == s. It means the host is empty, right? If you open a PR we can do code review easier.

Exactly p+1 == s is a better implement. I will open a PR just a moment later, along with the tests.