Skip to content

Commit

Permalink
Add RM_SetClientNameById and RM_GetClientNameById (#10839)
Browse files Browse the repository at this point in the history
Adding Module APIs to let the module read and set the client name of an arbitrary connection.
  • Loading branch information
zuiderkwast authored Jun 26, 2022
1 parent d96cf36 commit 6272ca6
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 15 deletions.
36 changes: 36 additions & 0 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -3373,6 +3373,40 @@ int RM_GetClientInfoById(void *ci, uint64_t id) {
return modulePopulateClientInfoStructure(ci,client,structver);
}

/* Returns the name of the client connection with the given ID.
*
* If the client ID does not exist or if the client has no name associated with
* it, NULL is returned. */
RedisModuleString *RM_GetClientNameById(RedisModuleCtx *ctx, uint64_t id) {
client *client = lookupClientByID(id);
if (client == NULL || client->name == NULL) return NULL;
robj *name = client->name;
incrRefCount(name);
autoMemoryAdd(ctx, REDISMODULE_AM_STRING, name);
return name;
}

/* Sets the name of the client with the given ID. This is equivalent to the client calling
* `CLIENT SETNAME name`.
*
* Returns REDISMODULE_OK on success. On failure, REDISMODULE_ERR is returned
* and errno is set as follows:
*
* - ENOENT if the client does not exist
* - EINVAL if the name contains invalid characters */
int RM_SetClientNameById(uint64_t id, RedisModuleString *name) {
client *client = lookupClientByID(id);
if (client == NULL) {
errno = ENOENT;
return REDISMODULE_ERR;
}
if (clientSetName(client, name) == C_ERR) {
errno = EINVAL;
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}

/* Publish a message to subscribers (see PUBLISH command). */
int RM_PublishMessage(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) {
UNUSED(ctx);
Expand Down Expand Up @@ -12601,6 +12635,8 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(ServerInfoGetFieldUnsigned);
REGISTER_API(ServerInfoGetFieldDouble);
REGISTER_API(GetClientInfoById);
REGISTER_API(GetClientNameById);
REGISTER_API(SetClientNameById);
REGISTER_API(PublishMessage);
REGISTER_API(PublishMessageShard);
REGISTER_API(SubscribeToServerEvent);
Expand Down
38 changes: 23 additions & 15 deletions src/networking.c
Original file line number Diff line number Diff line change
Expand Up @@ -2832,18 +2832,9 @@ sds getAllClientsInfoString(int type) {
return o;
}

/* This function implements CLIENT SETNAME, including replying to the
* user with an error if the charset is wrong (in that case C_ERR is
* returned). If the function succeeded C_OK is returned, and it's up
* to the caller to send a reply if needed.
*
* Setting an empty string as name has the effect of unsetting the
* currently set name: the client will remain unnamed.
*
* This function is also used to implement the HELLO SETNAME option. */
int clientSetNameOrReply(client *c, robj *name) {
int len = sdslen(name->ptr);
char *p = name->ptr;
/* Returns C_OK if the name has been set or C_ERR if the name is invalid. */
int clientSetName(client *c, robj *name) {
int len = (name != NULL) ? sdslen(name->ptr) : 0;

/* Setting the client name to an empty string actually removes
* the current name. */
Expand All @@ -2856,11 +2847,9 @@ int clientSetNameOrReply(client *c, robj *name) {
/* Otherwise check if the charset is ok. We need to do this otherwise
* CLIENT LIST format will break. You should always be able to
* split by space to get the different fields. */
char *p = name->ptr;
for (int j = 0; j < len; j++) {
if (p[j] < '!' || p[j] > '~') { /* ASCII is assumed. */
addReplyError(c,
"Client names cannot contain spaces, "
"newlines or special characters.");
return C_ERR;
}
}
Expand All @@ -2870,6 +2859,25 @@ int clientSetNameOrReply(client *c, robj *name) {
return C_OK;
}

/* This function implements CLIENT SETNAME, including replying to the
* user with an error if the charset is wrong (in that case C_ERR is
* returned). If the function succeeded C_OK is returned, and it's up
* to the caller to send a reply if needed.
*
* Setting an empty string as name has the effect of unsetting the
* currently set name: the client will remain unnamed.
*
* This function is also used to implement the HELLO SETNAME option. */
int clientSetNameOrReply(client *c, robj *name) {
int result = clientSetName(c, name);
if (result == C_ERR) {
addReplyError(c,
"Client names cannot contain spaces, "
"newlines or special characters.");
}
return result;
}

/* Reset the client state to resemble a newly connected client.
*/
void resetCommand(client *c) {
Expand Down
4 changes: 4 additions & 0 deletions src/redismodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,8 @@ REDISMODULE_API void (*RedisModule_ChannelAtPosWithFlags)(RedisModuleCtx *ctx, i
REDISMODULE_API unsigned long long (*RedisModule_GetClientId)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientUserNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetClientInfoById)(void *ci, uint64_t id) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetClientNameById)(uint64_t id, RedisModuleString *name) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_PublishMessageShard)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetContextFlags)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
Expand Down Expand Up @@ -1426,6 +1428,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(ServerInfoGetFieldUnsigned);
REDISMODULE_GET_API(ServerInfoGetFieldDouble);
REDISMODULE_GET_API(GetClientInfoById);
REDISMODULE_GET_API(GetClientNameById);
REDISMODULE_GET_API(SetClientNameById);
REDISMODULE_GET_API(PublishMessage);
REDISMODULE_GET_API(PublishMessageShard);
REDISMODULE_GET_API(SubscribeToServerEvent);
Expand Down
1 change: 1 addition & 0 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -2498,6 +2498,7 @@ char *getClientPeerId(client *client);
char *getClientSockName(client *client);
sds catClientInfoString(sds s, client *client);
sds getAllClientsInfoString(int type);
int clientSetName(client *c, robj *name);
void rewriteClientCommandVector(client *c, int argc, ...);
void rewriteClientCommandArgument(client *c, int i, robj *newval);
void replaceClientCommandVector(client *c, int argc, robj **argv);
Expand Down
25 changes: 25 additions & 0 deletions tests/modules/misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,27 @@ int test_clientinfo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_OK;
}

int test_getname(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
(void)argv;
if (argc != 1) return RedisModule_WrongArity(ctx);
unsigned long long id = RedisModule_GetClientId(ctx);
RedisModuleString *name = RedisModule_GetClientNameById(ctx, id);
if (name == NULL)
return RedisModule_ReplyWithError(ctx, "-ERR No name");
RedisModule_ReplyWithString(ctx, name);
RedisModule_FreeString(ctx, name);
return REDISMODULE_OK;
}

int test_setname(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
unsigned long long id = RedisModule_GetClientId(ctx);
if (RedisModule_SetClientNameById(id, argv[1]) == REDISMODULE_OK)
return RedisModule_ReplyWithSimpleString(ctx, "OK");
else
return RedisModule_ReplyWithError(ctx, strerror(errno));
}

int test_log_tsctx(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModuleCtx *tsctx = RedisModule_GetDetachedThreadSafeContext(ctx);
Expand Down Expand Up @@ -384,6 +405,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.clientinfo", test_clientinfo,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.getname", test_getname,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.setname", test_setname,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.redisversion", test_redisversion,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.getclientcert", test_getclientcert,"",0,0,0) == REDISMODULE_ERR)
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/moduleapi/misc.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ start_server {tags {"modules"}} {
assert { [dict get $info flags] == "${ssl_flag}::tracking::" }
}

test {test module get/set client name by id api} {
catch { r test.getname } e
assert_equal "-ERR No name" $e
r client setname nobody
catch { r test.setname "name with spaces" } e
assert_match "*Invalid argument*" $e
assert_equal nobody [r client getname]
assert_equal nobody [r test.getname]
r test.setname somebody
assert_equal somebody [r client getname]
}

test {test module getclientcert api} {
set cert [r test.getclientcert]

Expand Down

0 comments on commit 6272ca6

Please sign in to comment.