Skip to content

Commit 6819a68

Browse files
supporting more filters for Client List and Client Kill
Signed-off-by: Sarthak Aggarwal <[email protected]>
1 parent 52fd611 commit 6819a68

File tree

2 files changed

+346
-10
lines changed

2 files changed

+346
-10
lines changed

src/networking.c

+217-7
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ typedef struct {
6363
int type;
6464
/* Boolean flag to determine if the current client (`me`) should be filtered. 1 means "skip me", 0 means otherwise. */
6565
int skipme;
66+
/* Client name to filter. If NULL, no name filtering is applied. */
67+
char *name;
68+
/* Minimum idle time (in seconds) of a client connection for filtering.
69+
* Connections with idle time more than this value will match.
70+
* A value of 0 means no idle time filtering. */
71+
long long min_idle;
72+
/* Client flags for filtering. If NULL, no filtering is applied. */
73+
char *flags;
74+
/* Client pattern for filtering. If NULL, no filtering is applied. */
75+
robj *pattern;
76+
/* Client channel for filtering. If NULL, no filtering is applied. */
77+
robj *channel;
78+
/* Client shard channel for filtering. If NULL, no filtering is applied. */
79+
robj *shard_channel;
6680
} clientFilter;
6781

6882
static void clientCommandHelp(client *c);
@@ -91,6 +105,11 @@ char *getClientSockname(client *c);
91105
static int parseClientFiltersOrReply(client *c, int i, clientFilter *filter);
92106
static int clientMatchesFilter(client *client, clientFilter client_filter);
93107
sds getAllFilteredClientsInfoString(clientFilter *client_filter, int hide_user_data);
108+
static int clientMatchesFlagFilter(client *c, const char *flag_filter);
109+
static int clientSubscribedToChannel(client *client, robj *channel);
110+
static int clientSubscribedToShardChannel(client *client, robj *channel);
111+
static int clientSubscribedToPattern(client *client, robj *pattern);
112+
static void freeClientFilter(clientFilter *filter);
94113

95114
int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */
96115
__thread sds thread_shared_qb = NULL;
@@ -3687,6 +3706,34 @@ static int parseClientFiltersOrReply(client *c, int i, clientFilter *filter) {
36873706
return C_ERR;
36883707
}
36893708
i += 2;
3709+
} else if (!strcasecmp(c->argv[i]->ptr, "minidle") && moreargs) {
3710+
long long tmp;
3711+
3712+
if (getLongLongFromObjectOrReply(c, c->argv[i + 1], &tmp,
3713+
"minidle is not an integer or out of range") != C_OK)
3714+
return C_ERR;
3715+
if (tmp <= 0) {
3716+
addReplyError(c, "minidle should be greater than 0");
3717+
return C_ERR;
3718+
}
3719+
3720+
filter->min_idle = tmp;
3721+
i += 2;
3722+
} else if (!strcasecmp(c->argv[i]->ptr, "flags") && moreargs) {
3723+
filter->flags = c->argv[i + 1]->ptr;
3724+
i += 2;
3725+
} else if (!strcasecmp(c->argv[i]->ptr, "name") && moreargs) {
3726+
filter->name = c->argv[i + 1]->ptr;
3727+
i += 2;
3728+
} else if (!strcasecmp(c->argv[i]->ptr, "pattern") && moreargs) {
3729+
filter->pattern = createObject(OBJ_STRING, sdsnew(c->argv[i + 1]->ptr));
3730+
i += 2;
3731+
}else if (!strcasecmp(c->argv[i]->ptr, "channel") && moreargs) {
3732+
filter->channel = createObject(OBJ_STRING, sdsnew(c->argv[i + 1]->ptr));
3733+
i += 2;
3734+
}else if (!strcasecmp(c->argv[i]->ptr, "shardchannel") && moreargs) {
3735+
filter->shard_channel = createObject(OBJ_STRING, sdsnew(c->argv[i + 1]->ptr));
3736+
i += 2;
36903737
} else {
36913738
addReplyErrorObject(c, shared.syntaxerr);
36923739
return C_ERR;
@@ -3704,11 +3751,126 @@ static int clientMatchesFilter(client *client, clientFilter client_filter) {
37043751
if (client_filter.user && client->user != client_filter.user) return 0;
37053752
if (client_filter.skipme && client == server.current_client) return 0; // Skipme check
37063753
if (client_filter.max_age != 0 && (long long)(commandTimeSnapshot() / 1000 - client->ctime) < client_filter.max_age) return 0;
3754+
if (client_filter.min_idle != 0 && (long long)(commandTimeSnapshot() / 1000 - client->last_interaction) < client_filter.min_idle) return 0;
3755+
if (client_filter.flags && clientMatchesFlagFilter(client, client_filter.flags) == 0) return 0;
3756+
if (client_filter.name) {
3757+
if (!client->name || !client->name->ptr || strcmp(client->name->ptr, client_filter.name) != 0) {
3758+
return 0;
3759+
}
3760+
}
3761+
if (client_filter.pattern && !clientSubscribedToPattern(client, client_filter.pattern)) return 0;
3762+
if (client_filter.channel && !clientSubscribedToChannel(client, client_filter.channel)) return 0;
3763+
if (client_filter.shard_channel && !clientSubscribedToShardChannel(client, client_filter.shard_channel)) return 0;
37073764

37083765
// If all conditions are satisfied, the client matches the filter.
37093766
return 1;
37103767
}
37113768

3769+
/* Function to check if the client has the required flags as per the filter string */
3770+
static int clientMatchesFlagFilter(client *c, const char *flag_filter) {
3771+
// Iterate through the provided flag filter string
3772+
for (int i = 0; flag_filter[i] != '\0'; i++) {
3773+
const char flag = flag_filter[i];
3774+
3775+
// Check each flag
3776+
switch (flag) {
3777+
case 'O':
3778+
if (!(c->flag.replica && c->flag.monitor)) return 0;
3779+
break;
3780+
case 'S': // Replica flag
3781+
if (!c->flag.replica) return 0;
3782+
break;
3783+
case 'M': // Primary flag
3784+
if (!c->flag.primary) return 0;
3785+
break;
3786+
case 'P': // PubSub flag
3787+
if (!c->flag.pubsub) return 0;
3788+
break;
3789+
case 'x': // Multi flag
3790+
if (!c->flag.multi) return 0;
3791+
break;
3792+
case 'b': // Blocked flag
3793+
if (!c->flag.blocked) return 0;
3794+
break;
3795+
case 't': // Tracking flag
3796+
if (!c->flag.tracking) return 0;
3797+
break;
3798+
case 'R': // Invalid Client flag
3799+
if (!c->flag.tracking_broken_redir) return 0;
3800+
break;
3801+
case 'B': // Tracking Bcast flag
3802+
if (!c->flag.tracking_bcast) return 0;
3803+
break;
3804+
case 'd': // Dirty CAS flag
3805+
if (!c->flag.dirty_cas) return 0;
3806+
break;
3807+
case 'c': // Close after reply flag
3808+
if (!c->flag.close_after_reply) return 0;
3809+
break;
3810+
case 'u': // Unblocked flag
3811+
if (!c->flag.unblocked) return 0;
3812+
break;
3813+
case 'A': // Close ASAP flag
3814+
if (!c->flag.close_asap) return 0;
3815+
break;
3816+
case 'U': // Unix socket flag
3817+
if (!c->flag.unix_socket) return 0;
3818+
break;
3819+
case 'r': // Readonly flag
3820+
if (!c->flag.readonly) return 0;
3821+
break;
3822+
case 'e': // No evict flag
3823+
if (!c->flag.no_evict) return 0;
3824+
break;
3825+
case 'T': // No touch flag
3826+
if (!c->flag.no_touch) return 0;
3827+
break;
3828+
case 'I': // Import source flag
3829+
if (!c->flag.import_source) return 0;
3830+
break;
3831+
case 'N': // Check for no flags
3832+
if (!c->flag.replica && !c->flag.primary && !c->flag.pubsub &&
3833+
!c->flag.multi && !c->flag.blocked && !c->flag.tracking &&
3834+
!c->flag.tracking_broken_redir && !c->flag.tracking_bcast &&
3835+
!c->flag.dirty_cas && !c->flag.close_after_reply &&
3836+
!c->flag.unblocked && !c->flag.close_asap &&
3837+
!c->flag.unix_socket && !c->flag.readonly &&
3838+
!c->flag.no_evict && !c->flag.no_touch &&
3839+
!c->flag.import_source) {
3840+
return 1; // Matches 'N'
3841+
}
3842+
break;
3843+
default:
3844+
// Invalid flag, return false
3845+
return 0;
3846+
}
3847+
}
3848+
// If the loop completes, the client matches the flag filter
3849+
return 1;
3850+
}
3851+
3852+
static int clientSubscribedToChannel(client *client, robj *channel) {
3853+
if (client == NULL || client->pubsub_channels == NULL) {
3854+
return 0;
3855+
}
3856+
return dictFind(client->pubsub_channels, channel) != NULL;
3857+
}
3858+
3859+
static int clientSubscribedToShardChannel(client *client, robj *channel) {
3860+
if (client == NULL || client->pubsubshard_channels == NULL) {
3861+
return 0;
3862+
}
3863+
return dictFind(client->pubsubshard_channels, channel) != NULL;
3864+
}
3865+
3866+
static int clientSubscribedToPattern(client *client, robj *pattern) {
3867+
if (client == NULL || client->pubsub_patterns == NULL) {
3868+
return 0;
3869+
}
3870+
return dictFind(client->pubsub_patterns, pattern) != NULL;
3871+
}
3872+
3873+
37123874
static void clientCommandHelp(client *c) {
37133875
const char *help[] = {
37143876
"CACHING (YES|NO)",
@@ -3730,23 +3892,55 @@ static void clientCommandHelp(client *c) {
37303892
"KILL <option> <value> [<option> <value> [...]]",
37313893
" Kill connections. Options are:",
37323894
" * ADDR (<ip:port>|<unixsocket>:0)",
3733-
" Kill connections made from the specified address",
3895+
" Kill connections made from the specified address.",
37343896
" * LADDR (<ip:port>|<unixsocket>:0)",
3735-
" Kill connections made to specified local address",
3897+
" Kill connections made to the specified local address.",
37363898
" * TYPE (NORMAL|PRIMARY|REPLICA|PUBSUB)",
37373899
" Kill connections by type.",
37383900
" * USER <username>",
37393901
" Kill connections authenticated by <username>.",
37403902
" * SKIPME (YES|NO)",
3741-
" Skip killing current connection (default: yes).",
3903+
" Skip killing the current connection (default: yes).",
37423904
" * ID <client-id>",
3743-
" Kill connections by client id.",
3905+
" Kill connections by client ID.",
37443906
" * MAXAGE <maxage>",
37453907
" Kill connections older than the specified age.",
3908+
" * FLAGS <flags>",
3909+
" Kill connections that include the specified flags.",
3910+
" * NAME <client-name>",
3911+
" Kill connections with the specified name.",
3912+
" * PATTERN <pattern>",
3913+
" Kill connections subscribed to a matching pattern.",
3914+
" * CHANNEL <channel>",
3915+
" Kill connections subscribed to a matching channel.",
3916+
" * SHARD-CHANNEL <shard-channel>",
3917+
" Kill connections subscribed to a matching shard channel.",
37463918
"LIST [options ...]",
37473919
" Return information about client connections. Options:",
37483920
" * TYPE (NORMAL|PRIMARY|REPLICA|PUBSUB)",
37493921
" Return clients of specified type.",
3922+
" * USER <username>",
3923+
" Return clients authenticated by <username>.",
3924+
" * ADDR <ip:port>",
3925+
" Return clients connected from the specified address.",
3926+
" * LADDR <ip:port>",
3927+
" Return clients connected to the specified local address.",
3928+
" * ID <client-id>",
3929+
" Return clients with the specified IDs.",
3930+
" * SKIPME (YES|NO)",
3931+
" Exclude the current client from the list (default: no).",
3932+
" * FLAGS <flags>",
3933+
" Return clients with the specified flags.",
3934+
" * NAME <client-name>",
3935+
" Return clients with the specified name.",
3936+
" * MIN-IDLE <min-idle>",
3937+
" Return clients with idle time greater than or equal to <min-idle> seconds.",
3938+
" * PATTERN <pattern>",
3939+
" Return clients subscribed to a matching pattern.",
3940+
" * CHANNEL <channel>",
3941+
" Return clients subscribed to the specified channel.",
3942+
" * SHARD-CHANNEL <shard-channel>",
3943+
" Return clients subscribed to the specified shard channel.",
37503944
"UNPAUSE",
37513945
" Stop the current client pause, resuming traffic.",
37523946
"PAUSE <timeout> [WRITE|ALL]",
@@ -3798,11 +3992,11 @@ static void clientCommandList(client *c) {
37983992
int i = 2;
37993993

38003994
if (parseClientFiltersOrReply(c, i, &filter) != C_OK) {
3801-
zfree(filter.ids);
3995+
freeClientFilter(&filter);
38023996
return;
38033997
}
38043998
response = getAllFilteredClientsInfoString(&filter, 0);
3805-
zfree(filter.ids);
3999+
freeClientFilter(&filter);
38064000
} else if (c->argc != 2) {
38074001
addReplyErrorObject(c, shared.syntaxerr);
38084002
return;
@@ -3909,7 +4103,23 @@ static void clientCommandKill(client *c) {
39094103
* only after we queued the reply to its output buffers. */
39104104
if (close_this_client) c->flag.close_after_reply = 1;
39114105
client_kill_done:
3912-
zfree(client_filter.ids);
4106+
freeClientFilter(&client_filter);
4107+
}
4108+
4109+
static void freeClientFilter(clientFilter *filter) {
4110+
zfree(filter->ids);
4111+
if (filter->pattern) {
4112+
decrRefCount(filter->pattern);
4113+
filter->pattern = NULL;
4114+
}
4115+
if (filter->shard_channel) {
4116+
decrRefCount(filter->shard_channel);
4117+
filter->shard_channel = NULL;
4118+
}
4119+
if (filter->channel) {
4120+
decrRefCount(filter->channel);
4121+
filter->channel = NULL;
4122+
}
39134123
}
39144124

39154125

0 commit comments

Comments
 (0)