Add local address to CLIENT LIST, and a CLIENT KILL filter. (#7913)

Useful when you want to know through which bind address the client connected to
the server in case of multiple bind addresses.

- Adding `laddr` field to CLIENT list showing the local (bind) address.
- Adding `LADDR` option to CLIENT KILL to kill all the clients connected
  to a specific local address.
- Refactoring to share code.
This commit is contained in:
yoav-steinberg 2020-10-28 21:13:44 +02:00 committed by GitHub
parent 441bfa2dfb
commit 84b3c18f71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 71 additions and 65 deletions

View File

@ -592,11 +592,15 @@ int anetUnixAccept(char *err, int s) {
return fd;
}
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type) {
struct sockaddr_storage sa;
socklen_t salen = sizeof(sa);
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) goto error;
if (fd_to_str_type == FD_TO_PEER_NAME) {
if (getpeername(fd, (struct sockaddr *)&sa, &salen) == -1) goto error;
} else {
if (getsockname(fd, (struct sockaddr *)&sa, &salen) == -1) goto error;
}
if (ip_len == 0) goto error;
if (sa.ss_family == AF_INET) {
@ -608,7 +612,7 @@ int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
if (port) *port = ntohs(s->sin6_port);
} else if (sa.ss_family == AF_UNIX) {
if (ip) strncpy(ip,"/unixsocket",ip_len);
if (ip) snprintf(ip, ip_len, "/unixsocket");
if (port) *port = 0;
} else {
goto error;
@ -636,41 +640,11 @@ int anetFormatAddr(char *buf, size_t buf_len, char *ip, int port) {
"[%s]:%d" : "%s:%d", ip, port);
}
/* Like anetFormatAddr() but extract ip and port from the socket's peer. */
int anetFormatPeer(int fd, char *buf, size_t buf_len) {
/* Like anetFormatAddr() but extract ip and port from the socket's peer/sockname. */
int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type) {
char ip[INET6_ADDRSTRLEN];
int port;
anetPeerToString(fd,ip,sizeof(ip),&port);
anetFdToString(fd,ip,sizeof(ip),&port,fd_to_str_type);
return anetFormatAddr(buf, buf_len, ip, port);
}
int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
struct sockaddr_storage sa;
socklen_t salen = sizeof(sa);
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
if (port) *port = 0;
ip[0] = '?';
ip[1] = '\0';
return -1;
}
if (sa.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&sa;
if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
if (port) *port = ntohs(s->sin_port);
} else {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
if (port) *port = ntohs(s->sin6_port);
}
return 0;
}
int anetFormatSock(int fd, char *fmt, size_t fmt_len) {
char ip[INET6_ADDRSTRLEN];
int port;
anetSockName(fd,ip,sizeof(ip),&port);
return anetFormatAddr(fmt, fmt_len, ip, port);
}

View File

@ -49,6 +49,10 @@
#undef ip_len
#endif
/* FD to address string conversion types */
#define FD_TO_PEER_NAME 0
#define FD_TO_SOCK_NAME 1
int anetTcpConnect(char *err, const char *addr, int port);
int anetTcpNonBlockConnect(char *err, const char *addr, int port);
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
@ -71,11 +75,9 @@ int anetDisableTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
int anetSendTimeout(char *err, int fd, long long ms);
int anetRecvTimeout(char *err, int fd, long long ms);
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type);
int anetKeepAlive(char *err, int fd, int interval);
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port);
int anetFormatPeer(int fd, char *fmt, size_t fmt_len);
int anetFormatSock(int fd, char *fmt, size_t fmt_len);
int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type);
#endif

View File

@ -681,6 +681,7 @@ struct client *createAOFClient(void) {
c->obuf_soft_limit_reached_time = 0;
c->watched_keys = listCreate();
c->peerid = NULL;
c->sockname = NULL;
c->resp = 2;
c->user = NULL;
listSetFreeMethod(c->reply,freeClientReplyValue);

View File

@ -374,15 +374,15 @@ int connGetSocketError(connection *conn) {
}
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
return anetPeerToString(conn ? conn->fd : -1, ip, ip_len, port);
}
int connFormatPeer(connection *conn, char *buf, size_t buf_len) {
return anetFormatPeer(conn ? conn->fd : -1, buf, buf_len);
return anetFdToString(conn ? conn->fd : -1, ip, ip_len, port, FD_TO_PEER_NAME);
}
int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
return anetSockName(conn->fd, ip, ip_len, port);
return anetFdToString(conn->fd, ip, ip_len, port, FD_TO_SOCK_NAME);
}
int connFormatFdAddr(connection *conn, char *buf, size_t buf_len, int fd_to_str_type) {
return anetFormatFdAddr(conn ? conn->fd : -1, buf, buf_len, fd_to_str_type);
}
int connBlock(connection *conn) {

View File

@ -225,7 +225,7 @@ int connKeepAlive(connection *conn, int interval);
int connSendTimeout(connection *conn, long long ms);
int connRecvTimeout(connection *conn, long long ms);
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port);
int connFormatPeer(connection *conn, char *buf, size_t buf_len);
int connFormatFdAddr(connection *conn, char *buf, size_t buf_len, int fd_to_str_type);
int connSockName(connection *conn, char *ip, size_t ip_len, int *port);
const char *connGetInfo(connection *conn, char *buf, size_t buf_len);

View File

@ -168,6 +168,7 @@ client *createClient(connection *conn) {
c->pubsub_channels = dictCreate(&objectKeyPointerValueDictType,NULL);
c->pubsub_patterns = listCreate();
c->peerid = NULL;
c->sockname = NULL;
c->client_list_node = NULL;
c->client_tracking_redirection = 0;
c->client_tracking_prefixes = NULL;
@ -1300,6 +1301,7 @@ void freeClient(client *c) {
c->argv_len_sum = 0;
freeClientMultiState(c);
sdsfree(c->peerid);
sdsfree(c->sockname);
zfree(c);
}
@ -2079,25 +2081,25 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
*biggest_input_buffer = bib;
}
/* A Redis "Peer ID" is a colon separated ip:port pair.
/* A Redis "Address String" is a colon separated ip:port pair.
* For IPv4 it's in the form x.y.z.k:port, example: "127.0.0.1:1234".
* For IPv6 addresses we use [] around the IP part, like in "[::1]:1234".
* For Unix sockets we use path:0, like in "/tmp/redis:0".
*
* A Peer ID always fits inside a buffer of NET_PEER_ID_LEN bytes, including
* the null term.
* An Address String always fits inside a buffer of NET_ADDR_STR_LEN bytes,
* including the null term.
*
* On failure the function still populates 'peerid' with the "?:0" string
* in case you want to relax error checking or need to display something
* anyway (see anetPeerToString implementation for more info). */
void genClientPeerId(client *client, char *peerid,
size_t peerid_len) {
* On failure the function still populates 'addr' with the "?:0" string in case
* you want to relax error checking or need to display something anyway (see
* anetFdToString implementation for more info). */
void genClientAddrString(client *client, char *addr,
size_t addr_len, int fd_to_str_type) {
if (client->flags & CLIENT_UNIX_SOCKET) {
/* Unix socket client. */
snprintf(peerid,peerid_len,"%s:0",server.unixsocket);
snprintf(addr,addr_len,"%s:0",server.unixsocket);
} else {
/* TCP client. */
connFormatPeer(client->conn,peerid,peerid_len);
connFormatFdAddr(client->conn,addr,addr_len,fd_to_str_type);
}
}
@ -2106,15 +2108,29 @@ void genClientPeerId(client *client, char *peerid,
* The Peer ID never changes during the life of the client, however it
* is expensive to compute. */
char *getClientPeerId(client *c) {
char peerid[NET_PEER_ID_LEN];
char peerid[NET_ADDR_STR_LEN];
if (c->peerid == NULL) {
genClientPeerId(c,peerid,sizeof(peerid));
genClientAddrString(c,peerid,sizeof(peerid),FD_TO_PEER_NAME);
c->peerid = sdsnew(peerid);
}
return c->peerid;
}
/* This function returns the client bound socket name, by creating and caching
* it if client->sockname is NULL, otherwise returning the cached value.
* The Socket Name never changes during the life of the client, however it
* is expensive to compute. */
char *getClientSockname(client *c) {
char sockname[NET_ADDR_STR_LEN];
if (c->sockname == NULL) {
genClientAddrString(c,sockname,sizeof(sockname),FD_TO_SOCK_NAME);
c->sockname = sdsnew(sockname);
}
return c->sockname;
}
/* Concatenate a string representing the state of a client in a human
* readable format, into the sds string 's'. */
sds catClientInfoString(sds s, client *client) {
@ -2162,9 +2178,10 @@ sds catClientInfoString(sds s, client *client) {
total_mem += zmalloc_size(client->argv);
return sdscatfmt(s,
"id=%U addr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U argv-mem=%U obl=%U oll=%U omem=%U tot-mem=%U events=%s cmd=%s user=%s",
"id=%U addr=%s laddr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U argv-mem=%U obl=%U oll=%U omem=%U tot-mem=%U events=%s cmd=%s user=%s",
(unsigned long long) client->id,
getClientPeerId(client),
getClientSockname(client),
connGetInfo(client->conn, conninfo, sizeof(conninfo)),
client->name ? (char*)client->name->ptr : "",
(long long)(server.unixtime - client->ctime),
@ -2251,6 +2268,7 @@ void clientCommand(client *c) {
"KILL <ip:port> -- Kill connection made from <ip:port>.",
"KILL <option> <value> [option value ...] -- Kill connections. Options are:",
" ADDR <ip:port> -- Kill connection made from <ip:port>",
" LADDR <ip:port> -- Kill connection made to <ip:port>",
" TYPE (normal|master|replica|pubsub) -- Kill connections by type.",
" USER <username> -- Kill connections authenticated with such user.",
" SKIPME (yes|no) -- Skip killing current connection (default: yes).",
@ -2304,6 +2322,7 @@ NULL
/* CLIENT KILL <ip:port>
* CLIENT KILL <option> [value] ... <option> [value] */
char *addr = NULL;
char *laddr = NULL;
user *user = NULL;
int type = -1;
uint64_t id = 0;
@ -2336,6 +2355,8 @@ NULL
}
} else if (!strcasecmp(c->argv[i]->ptr,"addr") && moreargs) {
addr = c->argv[i+1]->ptr;
} else if (!strcasecmp(c->argv[i]->ptr,"laddr") && moreargs) {
laddr = c->argv[i+1]->ptr;
} else if (!strcasecmp(c->argv[i]->ptr,"user") && moreargs) {
user = ACLGetUserByName(c->argv[i+1]->ptr,
sdslen(c->argv[i+1]->ptr));
@ -2369,6 +2390,7 @@ NULL
while ((ln = listNext(&li)) != NULL) {
client *client = listNodeValue(ln);
if (addr && strcmp(getClientPeerId(client),addr) != 0) continue;
if (laddr && strcmp(getClientSockname(client),laddr) != 0) continue;
if (type != -1 && getClientType(client) != type) continue;
if (id != 0 && client->id != id) continue;
if (user && client->user != user) continue;

View File

@ -57,7 +57,7 @@ int RDBGeneratedByReplication = 0;
* IP address and its listening port which is more clear for the user, for
* example: "Closing connection with replica 10.1.2.3:6380". */
char *replicationGetSlaveName(client *c) {
static char buf[NET_PEER_ID_LEN];
static char buf[NET_ADDR_STR_LEN];
char ip[NET_IP_STR_LEN];
ip[0] = '\0';
@ -2792,6 +2792,11 @@ void replicationCacheMaster(client *c) {
sdsfree(c->peerid);
c->peerid = NULL;
}
/* Invalidate the Sock Name cache. */
if (c->sockname) {
sdsfree(c->sockname);
c->sockname = NULL;
}
/* Caching the master happens instead of the actual freeClient() call,
* so make sure to adjust the replication state. This function will

View File

@ -1194,7 +1194,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
sentinelRedisInstance *ri;
sentinelAddr *addr;
dict *table = NULL;
char slavename[NET_PEER_ID_LEN], *sdsname;
char slavename[NET_ADDR_STR_LEN], *sdsname;
serverAssert(flags & (SRI_MASTER|SRI_SLAVE|SRI_SENTINEL));
serverAssert((flags & SRI_MASTER) || master != NULL);
@ -1320,7 +1320,7 @@ sentinelRedisInstance *sentinelRedisInstanceLookupSlave(
{
sds key;
sentinelRedisInstance *slave;
char buf[NET_PEER_ID_LEN];
char buf[NET_ADDR_STR_LEN];
serverAssert(ri->flags & SRI_MASTER);
anetFormatAddr(buf,sizeof(buf),ip,port);
@ -2674,7 +2674,7 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
if (sentinel.announce_ip) {
announce_ip = sentinel.announce_ip;
} else {
if (anetSockName(ri->link->cc->c.fd,ip,sizeof(ip),NULL) == -1)
if (anetFdToString(ri->link->cc->c.fd,ip,sizeof(ip),NULL,FD_TO_SOCK_NAME) == -1)
return C_ERR;
announce_ip = ip;
}

View File

@ -112,7 +112,7 @@ typedef long long ustime_t; /* microsecond time type. */
#define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0
#define CONFIG_DEFAULT_LOGFILE ""
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
#define NET_PEER_ID_LEN (NET_IP_STR_LEN+32) /* Must be enough for ip:port */
#define NET_ADDR_STR_LEN (NET_IP_STR_LEN+32) /* Must be enough for ip:port */
#define CONFIG_BINDADDR_MAX 16
#define CONFIG_MIN_RESERVED_FDS 32
@ -850,6 +850,7 @@ typedef struct client {
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
sds peerid; /* Cached peer ID. */
sds sockname; /* Cached connection target address. */
listNode *client_list_node; /* list node in client list */
RedisModuleUserChangedFunc auth_callback; /* Module callback to execute
* when the authenticated user
@ -1716,6 +1717,7 @@ void *dupClientReplyValue(void *o);
void getClientsMaxBuffers(unsigned long *longest_output_list,
unsigned long *biggest_input_buffer);
char *getClientPeerId(client *client);
char *getClientSockName(client *client);
sds catClientInfoString(sds s, client *client);
sds getAllClientsInfoString(int type);
void rewriteClientCommandVector(client *c, int argc, ...);