From 6a8f068e36ba44e99366b97c52074700610f723d Mon Sep 17 00:00:00 2001 From: Sarthak Aggarwal Date: Wed, 15 Jan 2025 11:44:13 -0800 Subject: [PATCH] Adding Missing filters to CLIENT LIST and Dedup Parsing (#1401) Adds filter options to CLIENT LIST: * USER Return clients authenticated by . * ADDR Return clients connected from the specified address. * LADDR Return clients connected to the specified local address. * SKIPME (YES|NO) Exclude the current client from the list (default: no). * MAXAGE Only list connections older than the specified age. Modifies the ID filter to CLIENT KILL to allow multiple IDs * ID [...] Kill connections by client ids. This makes CLIENT LIST and CLIENT KILL accept the same options. For backward compatibility, the default value for SKIPME is NO for CLIENT LIST and YES for CLIENT KILL. The MAXAGE comes from CLIENT KILL, where it *keeps* clients with the given max age and kills the older ones. This logic becomes weird for CLIENT LIST, but is kept for similary with CLIENT KILL, for the use case of first testing manually using CLIENT LIST, and then running CLIENT KILL with the same filters. The `ID client-id [client-id ...]` no longer needs to be the last filter. The parsing logic determines if an argument is an ID or not based on whether it can be parsed as an integer or not. Partly addresses: #668 --------- Signed-off-by: Sarthak Aggarwal --- src/commands.def | 55 +- src/commands/client-caching.json | 2 +- src/commands/client-capa.json | 2 +- src/commands/client-getname.json | 2 +- src/commands/client-getredir.json | 2 +- src/commands/client-help.json | 2 +- src/commands/client-id.json | 2 +- src/commands/client-import-source.json | 2 +- src/commands/client-info.json | 2 +- src/commands/client-kill.json | 7 +- src/commands/client-list.json | 55 +- src/commands/client-no-evict.json | 2 +- src/commands/client-no-touch.json | 2 +- src/commands/client-pause.json | 2 +- src/commands/client-reply.json | 2 +- src/commands/client-setname.json | 2 +- src/commands/client-tracking.json | 2 +- src/commands/client-trackinginfo.json | 2 +- src/commands/client-unblock.json | 2 +- src/commands/client-unpause.json | 2 +- src/commands/client.json | 1 + src/networking.c | 1109 +++++++++++++----------- src/server.h | 19 + tests/unit/introspection.tcl | 122 ++- 24 files changed, 867 insertions(+), 535 deletions(-) diff --git a/src/commands.def b/src/commands.def index c5d766e3f8..cd919a80e1 100644 --- a/src/commands.def +++ b/src/commands.def @@ -1289,6 +1289,7 @@ commandHistory CLIENT_KILL_History[] = { {"6.2.0","`LADDR` option."}, {"8.0.0","`MAXAGE` option."}, {"8.0.0","Replaced `master` `TYPE` with `primary`. `master` still supported for backward compatibility."}, +{"8.1.0","`ID` option accepts multiple IDs."}, }; #endif @@ -1320,7 +1321,7 @@ struct COMMAND_ARG CLIENT_KILL_filter_new_format_skipme_Subargs[] = { /* CLIENT KILL filter new_format argument table */ struct COMMAND_ARG CLIENT_KILL_filter_new_format_Subargs[] = { -{MAKE_ARG("client-id",ARG_TYPE_INTEGER,-1,"ID",NULL,"2.8.12",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("client-id",ARG_TYPE_INTEGER,-1,"ID",NULL,"2.8.12",CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("client-type",ARG_TYPE_ONEOF,-1,"TYPE",NULL,"2.8.12",CMD_ARG_OPTIONAL,6,NULL),.subargs=CLIENT_KILL_filter_new_format_client_type_Subargs}, {MAKE_ARG("username",ARG_TYPE_STRING,-1,"USER",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("addr",ARG_TYPE_STRING,-1,"ADDR",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"}, @@ -1352,6 +1353,7 @@ commandHistory CLIENT_LIST_History[] = { {"7.0.0","Added `resp`, `multi-mem`, `rbs` and `rbp` fields."}, {"7.0.3","Added `ssub` field."}, {"8.0.0","Replaced `master` `TYPE` with `primary`. `master` still supported for backward compatibility."}, +{"8.1.0","Added filters USER, ADDR, LADDR, SKIPME, and MAXAGE"}, }; #endif @@ -1375,10 +1377,21 @@ struct COMMAND_ARG CLIENT_LIST_client_type_Subargs[] = { {MAKE_ARG("pubsub",ARG_TYPE_PURE_TOKEN,-1,"PUBSUB",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; +/* CLIENT LIST skipme argument table */ +struct COMMAND_ARG CLIENT_LIST_skipme_Subargs[] = { +{MAKE_ARG("yes",ARG_TYPE_PURE_TOKEN,-1,"YES",NULL,NULL,CMD_ARG_NONE,0,NULL)}, +{MAKE_ARG("no",ARG_TYPE_PURE_TOKEN,-1,"NO",NULL,NULL,CMD_ARG_NONE,0,NULL)}, +}; + /* CLIENT LIST argument table */ struct COMMAND_ARG CLIENT_LIST_Args[] = { {MAKE_ARG("client-type",ARG_TYPE_ONEOF,-1,"TYPE",NULL,"5.0.0",CMD_ARG_OPTIONAL,4,NULL),.subargs=CLIENT_LIST_client_type_Subargs}, {MAKE_ARG("client-id",ARG_TYPE_INTEGER,-1,"ID",NULL,"6.2.0",CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, +{MAKE_ARG("username",ARG_TYPE_STRING,-1,"USER",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, +{MAKE_ARG("addr",ARG_TYPE_STRING,-1,"ADDR",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"}, +{MAKE_ARG("laddr",ARG_TYPE_STRING,-1,"LADDR",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"}, +{MAKE_ARG("skipme",ARG_TYPE_ONEOF,-1,"SKIPME",NULL,"8.1.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=CLIENT_LIST_skipme_Subargs}, +{MAKE_ARG("maxage",ARG_TYPE_INTEGER,-1,"MAXAGE",NULL,"8.1.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** CLIENT NO_EVICT ********************/ @@ -1652,26 +1665,26 @@ struct COMMAND_ARG CLIENT_UNBLOCK_Args[] = { /* CLIENT command table */ struct COMMAND_STRUCT CLIENT_Subcommands[] = { -{MAKE_CMD("caching","Instructs the server whether to track the keys in the next request.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_CACHING_History,0,CLIENT_CACHING_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_CACHING_Keyspecs,0,NULL,1),.args=CLIENT_CACHING_Args}, -{MAKE_CMD("capa","A client claims its capability.","O(1)","8.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_CAPA_History,0,CLIENT_CAPA_Tips,0,clientCommand,-3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_CAPA_Keyspecs,0,NULL,1),.args=CLIENT_CAPA_Args}, -{MAKE_CMD("getname","Returns the name of the connection.","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETNAME_History,0,CLIENT_GETNAME_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETNAME_Keyspecs,0,NULL,0)}, -{MAKE_CMD("getredir","Returns the client ID to which the connection's tracking notifications are redirected.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETREDIR_History,0,CLIENT_GETREDIR_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETREDIR_Keyspecs,0,NULL,0)}, -{MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_HELP_History,0,CLIENT_HELP_Tips,0,clientCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_HELP_Keyspecs,0,NULL,0)}, -{MAKE_CMD("id","Returns the unique client ID of the connection.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_ID_History,0,CLIENT_ID_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_ID_Keyspecs,0,NULL,0)}, -{MAKE_CMD("import-source","Mark this client as an import source when server is in import mode.","O(1)","8.1.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_IMPORT_SOURCE_History,0,CLIENT_IMPORT_SOURCE_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_IMPORT_SOURCE_Keyspecs,0,NULL,1),.args=CLIENT_IMPORT_SOURCE_Args}, -{MAKE_CMD("info","Returns information about the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,0,CLIENT_INFO_Tips,1,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_INFO_Keyspecs,0,NULL,0)}, -{MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,7,CLIENT_KILL_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args}, -{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,7,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,2),.args=CLIENT_LIST_Args}, -{MAKE_CMD("no-evict","Sets the client eviction mode of the connection.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,0,CLIENT_NO_EVICT_Tips,0,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_NO_EVICT_Keyspecs,0,NULL,1),.args=CLIENT_NO_EVICT_Args}, -{MAKE_CMD("no-touch","Controls whether commands sent by the client affect the LRU/LFU of accessed keys.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,0,CLIENT_NO_TOUCH_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_NO_TOUCH_Keyspecs,0,NULL,1),.args=CLIENT_NO_TOUCH_Args}, -{MAKE_CMD("pause","Suspends commands processing.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,1,CLIENT_PAUSE_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_PAUSE_Keyspecs,0,NULL,2),.args=CLIENT_PAUSE_Args}, -{MAKE_CMD("reply","Instructs the server whether to reply to commands.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_REPLY_History,0,CLIENT_REPLY_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_REPLY_Keyspecs,0,NULL,1),.args=CLIENT_REPLY_Args}, +{MAKE_CMD("caching","Instructs the server whether to track the keys in the next request.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_CACHING_History,0,CLIENT_CACHING_Tips,0,clientCachingCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_CACHING_Keyspecs,0,NULL,1),.args=CLIENT_CACHING_Args}, +{MAKE_CMD("capa","A client claims its capability.","O(1)","8.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_CAPA_History,0,CLIENT_CAPA_Tips,0,clientCapaCommand,-3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_CAPA_Keyspecs,0,NULL,1),.args=CLIENT_CAPA_Args}, +{MAKE_CMD("getname","Returns the name of the connection.","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETNAME_History,0,CLIENT_GETNAME_Tips,0,clientGetNameCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETNAME_Keyspecs,0,NULL,0)}, +{MAKE_CMD("getredir","Returns the client ID to which the connection's tracking notifications are redirected.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETREDIR_History,0,CLIENT_GETREDIR_Tips,0,clientGetredirCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETREDIR_Keyspecs,0,NULL,0)}, +{MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_HELP_History,0,CLIENT_HELP_Tips,0,clientHelpCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_HELP_Keyspecs,0,NULL,0)}, +{MAKE_CMD("id","Returns the unique client ID of the connection.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_ID_History,0,CLIENT_ID_Tips,0,clientIDCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_ID_Keyspecs,0,NULL,0)}, +{MAKE_CMD("import-source","Mark this client as an import source when server is in import mode.","O(1)","8.1.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_IMPORT_SOURCE_History,0,CLIENT_IMPORT_SOURCE_Tips,0,clientImportSourceCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_IMPORT_SOURCE_Keyspecs,0,NULL,1),.args=CLIENT_IMPORT_SOURCE_Args}, +{MAKE_CMD("info","Returns information about the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,0,CLIENT_INFO_Tips,1,clientInfoCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_INFO_Keyspecs,0,NULL,0)}, +{MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,8,CLIENT_KILL_Tips,0,clientKillCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args}, +{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,8,CLIENT_LIST_Tips,1,clientListCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,7),.args=CLIENT_LIST_Args}, +{MAKE_CMD("no-evict","Sets the client eviction mode of the connection.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,0,CLIENT_NO_EVICT_Tips,0,clientNoEvictCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_NO_EVICT_Keyspecs,0,NULL,1),.args=CLIENT_NO_EVICT_Args}, +{MAKE_CMD("no-touch","Controls whether commands sent by the client affect the LRU/LFU of accessed keys.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,0,CLIENT_NO_TOUCH_Tips,0,clientNoTouchCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_NO_TOUCH_Keyspecs,0,NULL,1),.args=CLIENT_NO_TOUCH_Args}, +{MAKE_CMD("pause","Suspends commands processing.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,1,CLIENT_PAUSE_Tips,0,clientPauseCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_PAUSE_Keyspecs,0,NULL,2),.args=CLIENT_PAUSE_Args}, +{MAKE_CMD("reply","Instructs the server whether to reply to commands.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_REPLY_History,0,CLIENT_REPLY_Tips,0,clientReplyCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_REPLY_Keyspecs,0,NULL,1),.args=CLIENT_REPLY_Args}, {MAKE_CMD("setinfo","Sets information specific to the client or connection.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_SETINFO_History,0,CLIENT_SETINFO_Tips,2,clientSetinfoCommand,4,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_SETINFO_Keyspecs,0,NULL,1),.args=CLIENT_SETINFO_Args}, -{MAKE_CMD("setname","Sets the connection name.","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_SETNAME_History,0,CLIENT_SETNAME_Tips,2,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_SETNAME_Keyspecs,0,NULL,1),.args=CLIENT_SETNAME_Args}, -{MAKE_CMD("tracking","Controls server-assisted client-side caching for the connection.","O(1). Some options may introduce additional complexity.","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_TRACKING_History,0,CLIENT_TRACKING_Tips,0,clientCommand,-3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_TRACKING_Keyspecs,0,NULL,7),.args=CLIENT_TRACKING_Args}, -{MAKE_CMD("trackinginfo","Returns information about server-assisted client-side caching for the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_TRACKINGINFO_History,0,CLIENT_TRACKINGINFO_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_TRACKINGINFO_Keyspecs,0,NULL,0)}, -{MAKE_CMD("unblock","Unblocks a client blocked by a blocking command from a different connection.","O(log N) where N is the number of client connections","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_UNBLOCK_History,0,CLIENT_UNBLOCK_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_UNBLOCK_Keyspecs,0,NULL,2),.args=CLIENT_UNBLOCK_Args}, -{MAKE_CMD("unpause","Resumes processing commands from paused clients.","O(N) Where N is the number of paused clients","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_UNPAUSE_History,0,CLIENT_UNPAUSE_Tips,0,clientCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_UNPAUSE_Keyspecs,0,NULL,0)}, +{MAKE_CMD("setname","Sets the connection name.","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_SETNAME_History,0,CLIENT_SETNAME_Tips,2,clientSetNameCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_SETNAME_Keyspecs,0,NULL,1),.args=CLIENT_SETNAME_Args}, +{MAKE_CMD("tracking","Controls server-assisted client-side caching for the connection.","O(1). Some options may introduce additional complexity.","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_TRACKING_History,0,CLIENT_TRACKING_Tips,0,clientTrackingCommand,-3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_TRACKING_Keyspecs,0,NULL,7),.args=CLIENT_TRACKING_Args}, +{MAKE_CMD("trackinginfo","Returns information about server-assisted client-side caching for the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_TRACKINGINFO_History,0,CLIENT_TRACKINGINFO_Tips,0,clientTrackingInfoCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_TRACKINGINFO_Keyspecs,0,NULL,0)}, +{MAKE_CMD("unblock","Unblocks a client blocked by a blocking command from a different connection.","O(log N) where N is the number of client connections","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_UNBLOCK_History,0,CLIENT_UNBLOCK_Tips,0,clientUnblockCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_UNBLOCK_Keyspecs,0,NULL,2),.args=CLIENT_UNBLOCK_Args}, +{MAKE_CMD("unpause","Resumes processing commands from paused clients.","O(N) Where N is the number of paused clients","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_UNPAUSE_History,0,CLIENT_UNPAUSE_Tips,0,clientUnpauseCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_UNPAUSE_Keyspecs,0,NULL,0)}, {0} }; @@ -10910,7 +10923,7 @@ struct COMMAND_STRUCT serverCommandTable[] = { {MAKE_CMD("readwrite","Enables read-write queries for a connection to a Valkey replica node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,READWRITE_History,0,READWRITE_Tips,0,readwriteCommand,1,CMD_FAST|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,READWRITE_Keyspecs,0,NULL,0)}, /* connection */ {MAKE_CMD("auth","Authenticates the connection.","O(N) where N is the number of passwords defined for the user","1.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,AUTH_History,1,AUTH_Tips,0,authCommand,-2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_AUTH|CMD_SENTINEL|CMD_ALLOW_BUSY,ACL_CATEGORY_CONNECTION,AUTH_Keyspecs,0,NULL,2),.args=AUTH_Args}, -{MAKE_CMD("client","A container for client connection commands.","Depends on subcommand.","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_History,0,CLIENT_Tips,0,NULL,-2,CMD_SENTINEL,0,CLIENT_Keyspecs,0,NULL,0),.subcommands=CLIENT_Subcommands}, +{MAKE_CMD("client","A container for client connection commands.","Depends on subcommand.","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_History,0,CLIENT_Tips,0,clientCommand,-2,CMD_SENTINEL,0,CLIENT_Keyspecs,0,NULL,0),.subcommands=CLIENT_Subcommands}, {MAKE_CMD("echo","Returns the given string.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,ECHO_History,0,ECHO_Tips,0,echoCommand,2,CMD_LOADING|CMD_STALE|CMD_FAST,ACL_CATEGORY_CONNECTION,ECHO_Keyspecs,0,NULL,1),.args=ECHO_Args}, {MAKE_CMD("hello","Handshakes with the server.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,HELLO_History,2,HELLO_Tips,0,helloCommand,-1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_AUTH|CMD_SENTINEL|CMD_ALLOW_BUSY,ACL_CATEGORY_CONNECTION,HELLO_Keyspecs,0,NULL,1),.args=HELLO_Args}, {MAKE_CMD("ping","Returns the server's liveliness response.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,PING_History,0,PING_Tips,2,pingCommand,-1,CMD_FAST|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,PING_Keyspecs,0,NULL,1),.args=PING_Args}, diff --git a/src/commands/client-caching.json b/src/commands/client-caching.json index 2a4ae891db..d661492f45 100644 --- a/src/commands/client-caching.json +++ b/src/commands/client-caching.json @@ -6,7 +6,7 @@ "since": "6.0.0", "arity": 3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientCachingCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-capa.json b/src/commands/client-capa.json index 3c16cd44f9..0d0f577f94 100644 --- a/src/commands/client-capa.json +++ b/src/commands/client-capa.json @@ -6,7 +6,7 @@ "since": "8.0.0", "arity": -3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientCapaCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-getname.json b/src/commands/client-getname.json index 9e237af849..e13db064b7 100644 --- a/src/commands/client-getname.json +++ b/src/commands/client-getname.json @@ -6,7 +6,7 @@ "since": "2.6.9", "arity": 2, "container": "CLIENT", - "function": "clientCommand", + "function": "clientGetNameCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-getredir.json b/src/commands/client-getredir.json index 6fdb002dc8..3df1df6b6f 100644 --- a/src/commands/client-getredir.json +++ b/src/commands/client-getredir.json @@ -6,7 +6,7 @@ "since": "6.0.0", "arity": 2, "container": "CLIENT", - "function": "clientCommand", + "function": "clientGetredirCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-help.json b/src/commands/client-help.json index b49294c9ee..ae771d52ae 100644 --- a/src/commands/client-help.json +++ b/src/commands/client-help.json @@ -6,7 +6,7 @@ "since": "5.0.0", "arity": 2, "container": "CLIENT", - "function": "clientCommand", + "function": "clientHelpCommand", "command_flags": [ "LOADING", "STALE", diff --git a/src/commands/client-id.json b/src/commands/client-id.json index 7c2bf08200..f6131250dd 100644 --- a/src/commands/client-id.json +++ b/src/commands/client-id.json @@ -6,7 +6,7 @@ "since": "5.0.0", "arity": 2, "container": "CLIENT", - "function": "clientCommand", + "function": "clientIDCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-import-source.json b/src/commands/client-import-source.json index 113c07d70a..dd5ef65e77 100644 --- a/src/commands/client-import-source.json +++ b/src/commands/client-import-source.json @@ -6,7 +6,7 @@ "since": "8.1.0", "arity": 3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientImportSourceCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-info.json b/src/commands/client-info.json index f974da437b..afda2ca967 100644 --- a/src/commands/client-info.json +++ b/src/commands/client-info.json @@ -6,7 +6,7 @@ "since": "6.2.0", "arity": 2, "container": "CLIENT", - "function": "clientCommand", + "function": "clientInfoCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-kill.json b/src/commands/client-kill.json index 97fa932cd8..0ae3579534 100644 --- a/src/commands/client-kill.json +++ b/src/commands/client-kill.json @@ -6,7 +6,7 @@ "since": "2.4.0", "arity": -3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientKillCommand", "history": [ [ "2.8.12", @@ -35,6 +35,10 @@ [ "8.0.0", "Replaced `master` `TYPE` with `primary`. `master` still supported for backward compatibility." + ], + [ + "8.1.0", + "`ID` option accepts multiple IDs." ] ], "command_flags": [ @@ -68,6 +72,7 @@ "name": "client-id", "type": "integer", "optional": true, + "multiple": true, "since": "2.8.12" }, { diff --git a/src/commands/client-list.json b/src/commands/client-list.json index d9c0054e60..05e4de2419 100644 --- a/src/commands/client-list.json +++ b/src/commands/client-list.json @@ -6,7 +6,7 @@ "since": "2.4.0", "arity": -2, "container": "CLIENT", - "function": "clientCommand", + "function": "clientListCommand", "history": [ [ "2.8.12", @@ -35,6 +35,10 @@ [ "8.0.0", "Replaced `master` `TYPE` with `primary`. `master` still supported for backward compatibility." + ], + [ + "8.1.0", + "Added filters USER, ADDR, LADDR, SKIPME, and MAXAGE" ] ], "command_flags": [ @@ -91,6 +95,55 @@ "optional": true, "multiple": true, "since": "6.2.0" + }, + { + "token": "USER", + "name": "username", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "ADDR", + "name": "addr", + "display": "ip:port", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "LADDR", + "name": "laddr", + "display": "ip:port", + "type": "string", + "optional": true, + "since": "8.1.0" + }, + { + "token": "SKIPME", + "name": "skipme", + "type": "oneof", + "optional": true, + "since": "8.1.0", + "arguments": [ + { + "name": "yes", + "type": "pure-token", + "token": "YES" + }, + { + "name": "no", + "type": "pure-token", + "token": "NO" + } + ] + }, + { + "token": "MAXAGE", + "name": "maxage", + "type": "integer", + "optional": true, + "since": "8.1.0" } ] } diff --git a/src/commands/client-no-evict.json b/src/commands/client-no-evict.json index 9ed6718405..710f8a97f9 100644 --- a/src/commands/client-no-evict.json +++ b/src/commands/client-no-evict.json @@ -6,7 +6,7 @@ "since": "7.0.0", "arity": 3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientNoEvictCommand", "command_flags": [ "ADMIN", "NOSCRIPT", diff --git a/src/commands/client-no-touch.json b/src/commands/client-no-touch.json index 4cf7b72416..4196770a2e 100644 --- a/src/commands/client-no-touch.json +++ b/src/commands/client-no-touch.json @@ -6,7 +6,7 @@ "since": "7.2.0", "arity": 3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientNoTouchCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-pause.json b/src/commands/client-pause.json index b1dd7bc478..54faf796c2 100644 --- a/src/commands/client-pause.json +++ b/src/commands/client-pause.json @@ -6,7 +6,7 @@ "since": "3.0.0", "arity": -3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientPauseCommand", "history": [ [ "6.2.0", diff --git a/src/commands/client-reply.json b/src/commands/client-reply.json index 9406de85cf..8d2b713a69 100644 --- a/src/commands/client-reply.json +++ b/src/commands/client-reply.json @@ -6,7 +6,7 @@ "since": "3.2.0", "arity": 3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientReplyCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-setname.json b/src/commands/client-setname.json index b071bd18ff..f544dc6a0f 100644 --- a/src/commands/client-setname.json +++ b/src/commands/client-setname.json @@ -6,7 +6,7 @@ "since": "2.6.9", "arity": 3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientSetNameCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-tracking.json b/src/commands/client-tracking.json index 2c3768c2fb..1acf84fafc 100644 --- a/src/commands/client-tracking.json +++ b/src/commands/client-tracking.json @@ -6,7 +6,7 @@ "since": "6.0.0", "arity": -3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientTrackingCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-trackinginfo.json b/src/commands/client-trackinginfo.json index 270a3d5e6e..78ba8201d7 100644 --- a/src/commands/client-trackinginfo.json +++ b/src/commands/client-trackinginfo.json @@ -6,7 +6,7 @@ "since": "6.2.0", "arity": 2, "container": "CLIENT", - "function": "clientCommand", + "function": "clientTrackingInfoCommand", "command_flags": [ "NOSCRIPT", "LOADING", diff --git a/src/commands/client-unblock.json b/src/commands/client-unblock.json index d391ede9e9..2173173f40 100644 --- a/src/commands/client-unblock.json +++ b/src/commands/client-unblock.json @@ -6,7 +6,7 @@ "since": "5.0.0", "arity": -3, "container": "CLIENT", - "function": "clientCommand", + "function": "clientUnblockCommand", "command_flags": [ "ADMIN", "NOSCRIPT", diff --git a/src/commands/client-unpause.json b/src/commands/client-unpause.json index 6c55210d2a..bb78fb848b 100644 --- a/src/commands/client-unpause.json +++ b/src/commands/client-unpause.json @@ -6,7 +6,7 @@ "since": "6.2.0", "arity": 2, "container": "CLIENT", - "function": "clientCommand", + "function": "clientUnpauseCommand", "command_flags": [ "ADMIN", "NOSCRIPT", diff --git a/src/commands/client.json b/src/commands/client.json index b50996128e..116fb4d4a2 100644 --- a/src/commands/client.json +++ b/src/commands/client.json @@ -4,6 +4,7 @@ "complexity": "Depends on subcommand.", "group": "connection", "since": "2.4.0", + "function": "clientCommand", "arity": -2, "command_flags": [ "SENTINEL" diff --git a/src/networking.c b/src/networking.c index 48e397e6f4..0ff0fed4c0 100644 --- a/src/networking.c +++ b/src/networking.c @@ -31,6 +31,7 @@ #include "cluster.h" #include "cluster_slot_stats.h" #include "script.h" +#include "intset.h" #include "sds.h" #include "fpconv_dtoa.h" #include "fmtargs.h" @@ -43,10 +44,35 @@ #include #include +/* This struct is used to encapsulate filtering criteria for operations on clients + * such as identifying specific clients to kill or retrieve. Each field in the struct + * represents a filter that can be applied based on specific attributes of a client. */ +typedef struct { + /* A set of client IDs to filter. If NULL, no ID filtering is applied. */ + intset *ids; + /* Maximum age (in seconds) of a client connection for filtering. + * Connections younger than this value will not match. + * A value of 0 means no age filtering. */ + long long max_age; + /* Address/port of the client. If NULL, no address filtering is applied. */ + char *addr; + /* Remote address/port of the client. If NULL, no address filtering is applied. */ + char *laddr; + /* Filtering clients by authentication user. If NULL, no user-based filtering is applied. */ + user *user; + /* Client type to filter. If set to -1, no type filtering is applied. */ + int type; + /* Boolean flag to determine if the current client (`me`) should be filtered. 1 means "skip me", 0 means otherwise. */ + int skipme; +} clientFilter; + static void setProtocolError(const char *errstr, client *c); static void pauseClientsByClient(mstime_t end, int isPauseClientAll); int postponeClientRead(client *c); char *getClientSockname(client *c); +static int parseClientFiltersOrReply(client *c, int index, clientFilter *filter); +static int clientMatchesFilter(client *client, clientFilter client_filter); +static sds getAllFilteredClientsInfoString(clientFilter *client_filter, int hide_user_data); int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */ __thread sds thread_shared_qb = NULL; @@ -2451,6 +2477,7 @@ int handleClientsWithPendingWrites(void) { /* resetClient prepare the client to process the next command */ void resetClient(client *c) { serverCommandProc *prevcmd = c->cmd ? c->cmd->proc : NULL; + serverCommandProc *prevParentCmd = c->cmd && c->cmd->parent ? c->cmd->parent->proc : NULL; freeClientArgv(c); freeClientOriginalArgv(c); @@ -2480,7 +2507,7 @@ void resetClient(client *c) { /* We do the same for the CACHING command as well. It also affects * the next command or transaction executed, in a way very similar * to ASKING. */ - if (!c->flag.multi && prevcmd != clientCommand) c->flag.tracking_caching = 0; + if (!c->flag.multi && prevParentCmd != clientCommand) c->flag.tracking_caching = 0; /* Remove the CLIENT_REPLY_SKIP flag if any so that the reply * to the next command will be sent, but set the flag if the command @@ -3354,6 +3381,22 @@ sds getAllClientsInfoString(int type, int hide_user_data) { return o; } +static sds getAllFilteredClientsInfoString(clientFilter *client_filter, int hide_user_data) { + listNode *ln; + listIter li; + client *client; + sds o = sdsempty(); + sdsclear(o); + listRewind(server.clients, &li); + while ((ln = listNext(&li)) != NULL) { + client = listNodeValue(ln); + if (!clientMatchesFilter(client, *client_filter)) continue; + o = catClientInfoString(o, client, hide_user_data); + o = sdscatlen(o, "\n", 1); + } + return o; +} + /* Check validity of an attribute that's gonna be shown in CLIENT LIST. */ int validateClientAttr(const char *val) { /* Check if the charset is ok. We need to do this otherwise @@ -3473,570 +3516,648 @@ void quitCommand(client *c) { c->flag.close_after_reply = 1; } -void clientCommand(client *c) { - listNode *ln; - listIter li; +static int parseClientFiltersOrReply(client *c, int index, clientFilter *filter) { + while (index < c->argc) { + int moreargs = c->argc > index + 1; - if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr, "help")) { - const char *help[] = { - "CACHING (YES|NO)", - " Enable/disable tracking of the keys for next command in OPTIN/OPTOUT modes.", - "CAPA