mirror_iproute2/ip/tcp_metrics.c
Hangbin Liu 86bf43c7c2 lib/libnetlink: update rtnl_talk to support malloc buff at run time
This is an update for 460c03f3f3 ("iplink: double the buffer size also in
iplink_get()"). After update, we will not need to double the buffer size
every time when VFs number increased.

With call like rtnl_talk(&rth, &req.n, NULL, 0), we can simply remove the
length parameter.

With call like rtnl_talk(&rth, nlh, nlh, sizeof(req), I add a new variable
answer to avoid overwrite data in nlh, because it may has more info after
nlh. also this will avoid nlh buffer not enough issue.

We need to free answer after using.

Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
Signed-off-by: Phil Sutter <phil@nwl.cc>
2017-10-26 12:29:29 +02:00

503 lines
12 KiB
C

/*
* tcp_metrics.c "ip tcp_metrics/tcpmetrics"
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation;
*
* Authors: Julian Anastasov <ja@ssi.bg>, August 2012
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <linux/genetlink.h>
#include <linux/tcp_metrics.h>
#include "utils.h"
#include "ip_common.h"
#include "libgenl.h"
static void usage(void)
{
fprintf(stderr, "Usage: ip tcp_metrics/tcpmetrics { COMMAND | help }\n");
fprintf(stderr, " ip tcp_metrics { show | flush } SELECTOR\n");
fprintf(stderr, " ip tcp_metrics delete [ address ] ADDRESS\n");
fprintf(stderr, "SELECTOR := [ [ address ] PREFIX ]\n");
exit(-1);
}
/* netlink socket */
static struct rtnl_handle grth = { .fd = -1 };
static int genl_family = -1;
#define TCPM_REQUEST(_req, _bufsiz, _cmd, _flags) \
GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
TCP_METRICS_GENL_VERSION, _cmd, _flags)
#define CMD_LIST 0x0001 /* list, lst, show */
#define CMD_DEL 0x0002 /* delete, remove */
#define CMD_FLUSH 0x0004 /* flush */
static struct {
char *name;
int code;
} cmds[] = {
{ "list", CMD_LIST },
{ "lst", CMD_LIST },
{ "show", CMD_LIST },
{ "delete", CMD_DEL },
{ "remove", CMD_DEL },
{ "flush", CMD_FLUSH },
};
static char *metric_name[TCP_METRIC_MAX + 1] = {
[TCP_METRIC_RTT] = "rtt",
[TCP_METRIC_RTTVAR] = "rttvar",
[TCP_METRIC_SSTHRESH] = "ssthresh",
[TCP_METRIC_CWND] = "cwnd",
[TCP_METRIC_REORDERING] = "reordering",
};
static struct
{
int flushed;
char *flushb;
int flushp;
int flushe;
int cmd;
inet_prefix daddr;
inet_prefix saddr;
} f;
static int flush_update(void)
{
if (rtnl_send_check(&grth, f.flushb, f.flushp) < 0) {
perror("Failed to send flush request\n");
return -1;
}
f.flushp = 0;
return 0;
}
static int process_msg(const struct sockaddr_nl *who, struct nlmsghdr *n,
void *arg)
{
FILE *fp = (FILE *) arg;
struct genlmsghdr *ghdr;
struct rtattr *attrs[TCP_METRICS_ATTR_MAX + 1], *a;
int len = n->nlmsg_len;
inet_prefix daddr, saddr;
int family, i, atype, stype, dlen = 0, slen = 0;
if (n->nlmsg_type != genl_family)
return -1;
len -= NLMSG_LENGTH(GENL_HDRLEN);
if (len < 0)
return -1;
ghdr = NLMSG_DATA(n);
if (ghdr->cmd != TCP_METRICS_CMD_GET)
return 0;
parse_rtattr(attrs, TCP_METRICS_ATTR_MAX, (void *) ghdr + GENL_HDRLEN,
len);
if (attrs[TCP_METRICS_ATTR_ADDR_IPV4]) {
if (f.daddr.family && f.daddr.family != AF_INET)
return 0;
a = attrs[TCP_METRICS_ATTR_ADDR_IPV4];
memcpy(&daddr.data, RTA_DATA(a), 4);
daddr.bytelen = 4;
family = AF_INET;
atype = TCP_METRICS_ATTR_ADDR_IPV4;
dlen = RTA_PAYLOAD(a);
} else if (attrs[TCP_METRICS_ATTR_ADDR_IPV6]) {
if (f.daddr.family && f.daddr.family != AF_INET6)
return 0;
a = attrs[TCP_METRICS_ATTR_ADDR_IPV6];
memcpy(&daddr.data, RTA_DATA(a), 16);
daddr.bytelen = 16;
family = AF_INET6;
atype = TCP_METRICS_ATTR_ADDR_IPV6;
dlen = RTA_PAYLOAD(a);
} else {
return 0;
}
if (attrs[TCP_METRICS_ATTR_SADDR_IPV4]) {
if (f.saddr.family && f.saddr.family != AF_INET)
return 0;
a = attrs[TCP_METRICS_ATTR_SADDR_IPV4];
memcpy(&saddr.data, RTA_DATA(a), 4);
saddr.bytelen = 4;
stype = TCP_METRICS_ATTR_SADDR_IPV4;
slen = RTA_PAYLOAD(a);
} else if (attrs[TCP_METRICS_ATTR_SADDR_IPV6]) {
if (f.saddr.family && f.saddr.family != AF_INET6)
return 0;
a = attrs[TCP_METRICS_ATTR_SADDR_IPV6];
memcpy(&saddr.data, RTA_DATA(a), 16);
saddr.bytelen = 16;
stype = TCP_METRICS_ATTR_SADDR_IPV6;
slen = RTA_PAYLOAD(a);
}
if (f.daddr.family && f.daddr.bitlen >= 0 &&
inet_addr_match(&daddr, &f.daddr, f.daddr.bitlen))
return 0;
/* Only check for the source-address if the kernel supports it,
* meaning slen != 0.
*/
if (slen && f.saddr.family && f.saddr.bitlen >= 0 &&
inet_addr_match(&saddr, &f.saddr, f.saddr.bitlen))
return 0;
if (f.flushb) {
struct nlmsghdr *fn;
TCPM_REQUEST(req2, 128, TCP_METRICS_CMD_DEL, NLM_F_REQUEST);
addattr_l(&req2.n, sizeof(req2), atype, &daddr.data,
daddr.bytelen);
if (slen)
addattr_l(&req2.n, sizeof(req2), stype, &saddr.data,
saddr.bytelen);
if (NLMSG_ALIGN(f.flushp) + req2.n.nlmsg_len > f.flushe) {
if (flush_update())
return -1;
}
fn = (struct nlmsghdr *) (f.flushb + NLMSG_ALIGN(f.flushp));
memcpy(fn, &req2.n, req2.n.nlmsg_len);
fn->nlmsg_seq = ++grth.seq;
f.flushp = (((char *) fn) + req2.n.nlmsg_len) - f.flushb;
f.flushed++;
if (show_stats < 2)
return 0;
}
if (f.cmd & (CMD_DEL | CMD_FLUSH))
fprintf(fp, "Deleted ");
fprintf(fp, "%s",
format_host(family, dlen, &daddr.data));
a = attrs[TCP_METRICS_ATTR_AGE];
if (a) {
unsigned long long val = rta_getattr_u64(a);
fprintf(fp, " age %llu.%03llusec",
val / 1000, val % 1000);
}
a = attrs[TCP_METRICS_ATTR_TW_TS_STAMP];
if (a) {
__s32 val = (__s32) rta_getattr_u32(a);
__u32 tsval;
a = attrs[TCP_METRICS_ATTR_TW_TSVAL];
tsval = a ? rta_getattr_u32(a) : 0;
fprintf(fp, " tw_ts %u/%dsec ago", tsval, val);
}
a = attrs[TCP_METRICS_ATTR_VALS];
if (a) {
struct rtattr *m[TCP_METRIC_MAX + 1 + 1];
unsigned long rtt = 0, rttvar = 0;
parse_rtattr_nested(m, TCP_METRIC_MAX + 1, a);
for (i = 0; i < TCP_METRIC_MAX + 1; i++) {
unsigned long val;
a = m[i + 1];
if (!a)
continue;
if (i != TCP_METRIC_RTT &&
i != TCP_METRIC_RTT_US &&
i != TCP_METRIC_RTTVAR &&
i != TCP_METRIC_RTTVAR_US) {
if (metric_name[i])
fprintf(fp, " %s ", metric_name[i]);
else
fprintf(fp, " metric_%d ", i);
}
val = rta_getattr_u32(a);
switch (i) {
case TCP_METRIC_RTT:
if (!rtt)
rtt = (val * 1000UL) >> 3;
break;
case TCP_METRIC_RTTVAR:
if (!rttvar)
rttvar = (val * 1000UL) >> 2;
break;
case TCP_METRIC_RTT_US:
rtt = val >> 3;
break;
case TCP_METRIC_RTTVAR_US:
rttvar = val >> 2;
break;
case TCP_METRIC_SSTHRESH:
case TCP_METRIC_CWND:
case TCP_METRIC_REORDERING:
default:
fprintf(fp, "%lu", val);
break;
}
}
if (rtt)
fprintf(fp, " rtt %luus", rtt);
if (rttvar)
fprintf(fp, " rttvar %luus", rttvar);
}
a = attrs[TCP_METRICS_ATTR_FOPEN_MSS];
if (a)
fprintf(fp, " fo_mss %u", rta_getattr_u16(a));
a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROPS];
if (a) {
__u16 syn_loss = rta_getattr_u16(a);
unsigned long long ts;
a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROP_TS];
ts = a ? rta_getattr_u64(a) : 0;
fprintf(fp, " fo_syn_drops %u/%llu.%03llusec ago",
syn_loss, ts / 1000, ts % 1000);
}
a = attrs[TCP_METRICS_ATTR_FOPEN_COOKIE];
if (a) {
char cookie[32 + 1];
unsigned char *ptr = RTA_DATA(a);
int i, max = RTA_PAYLOAD(a);
if (max > 16)
max = 16;
cookie[0] = 0;
for (i = 0; i < max; i++)
sprintf(cookie + i + i, "%02x", ptr[i]);
fprintf(fp, " fo_cookie %s", cookie);
}
if (slen) {
fprintf(fp, " source %s",
format_host(family, slen, &saddr.data));
}
fprintf(fp, "\n");
fflush(fp);
return 0;
}
static int tcpm_do_cmd(int cmd, int argc, char **argv)
{
TCPM_REQUEST(req, 1024, TCP_METRICS_CMD_GET, NLM_F_REQUEST);
struct nlmsghdr *answer;
int atype = -1, stype = -1;
int ack;
memset(&f, 0, sizeof(f));
f.daddr.bitlen = -1;
f.daddr.family = preferred_family;
f.saddr.bitlen = -1;
f.saddr.family = preferred_family;
switch (preferred_family) {
case AF_UNSPEC:
case AF_INET:
case AF_INET6:
break;
default:
fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
return -1;
}
for (; argc > 0; argc--, argv++) {
if (strcmp(*argv, "src") == 0 ||
strcmp(*argv, "source") == 0) {
char *who = *argv;
NEXT_ARG();
if (matches(*argv, "help") == 0)
usage();
if (f.saddr.bitlen >= 0)
duparg2(who, *argv);
get_prefix(&f.saddr, *argv, preferred_family);
if (f.saddr.bytelen && f.saddr.bytelen * 8 == f.saddr.bitlen) {
if (f.saddr.family == AF_INET)
stype = TCP_METRICS_ATTR_SADDR_IPV4;
else if (f.saddr.family == AF_INET6)
stype = TCP_METRICS_ATTR_SADDR_IPV6;
}
if (stype < 0) {
fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
*argv);
return -1;
}
} else {
char *who = "address";
if (strcmp(*argv, "addr") == 0 ||
strcmp(*argv, "address") == 0) {
who = *argv;
NEXT_ARG();
}
if (matches(*argv, "help") == 0)
usage();
if (f.daddr.bitlen >= 0)
duparg2(who, *argv);
get_prefix(&f.daddr, *argv, preferred_family);
if (f.daddr.bytelen && f.daddr.bytelen * 8 == f.daddr.bitlen) {
if (f.daddr.family == AF_INET)
atype = TCP_METRICS_ATTR_ADDR_IPV4;
else if (f.daddr.family == AF_INET6)
atype = TCP_METRICS_ATTR_ADDR_IPV6;
}
if ((CMD_DEL & cmd) && atype < 0) {
fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
*argv);
return -1;
}
}
argc--; argv++;
}
if (cmd == CMD_DEL && atype < 0)
missarg("address");
/* flush for exact address ? Single del */
if (cmd == CMD_FLUSH && atype >= 0)
cmd = CMD_DEL;
/* flush for all addresses ? Single del without address */
if (cmd == CMD_FLUSH && f.daddr.bitlen <= 0 &&
f.saddr.bitlen <= 0 && preferred_family == AF_UNSPEC) {
cmd = CMD_DEL;
req.g.cmd = TCP_METRICS_CMD_DEL;
ack = 1;
} else if (cmd == CMD_DEL) {
req.g.cmd = TCP_METRICS_CMD_DEL;
ack = 1;
} else { /* CMD_FLUSH, CMD_LIST */
ack = 0;
}
if (genl_init_handle(&grth, TCP_METRICS_GENL_NAME, &genl_family))
exit(1);
req.n.nlmsg_type = genl_family;
if (!(cmd & CMD_FLUSH) && (atype >= 0 || (cmd & CMD_DEL))) {
if (ack)
req.n.nlmsg_flags |= NLM_F_ACK;
if (atype >= 0)
addattr_l(&req.n, sizeof(req), atype, &f.daddr.data,
f.daddr.bytelen);
if (stype >= 0)
addattr_l(&req.n, sizeof(req), stype, &f.saddr.data,
f.saddr.bytelen);
} else {
req.n.nlmsg_flags |= NLM_F_DUMP;
}
f.cmd = cmd;
if (cmd & CMD_FLUSH) {
int round = 0;
char flushb[4096-512];
f.flushb = flushb;
f.flushp = 0;
f.flushe = sizeof(flushb);
for (;;) {
req.n.nlmsg_seq = grth.dump = ++grth.seq;
if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
perror("Failed to send flush request");
exit(1);
}
f.flushed = 0;
if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
fprintf(stderr, "Flush terminated\n");
exit(1);
}
if (f.flushed == 0) {
if (round == 0) {
fprintf(stderr, "Nothing to flush.\n");
} else if (show_stats)
printf("*** Flush is complete after %d round%s ***\n",
round, round > 1 ? "s" : "");
fflush(stdout);
return 0;
}
round++;
if (flush_update() < 0)
exit(1);
if (show_stats) {
printf("\n*** Round %d, deleting %d entries ***\n",
round, f.flushed);
fflush(stdout);
}
}
return 0;
}
if (ack) {
if (rtnl_talk(&grth, &req.n, NULL) < 0)
return -2;
} else if (atype >= 0) {
if (rtnl_talk(&grth, &req.n, &answer) < 0)
return -2;
if (process_msg(NULL, answer, stdout) < 0) {
fprintf(stderr, "Dump terminated\n");
exit(1);
}
free(answer);
} else {
req.n.nlmsg_seq = grth.dump = ++grth.seq;
if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
perror("Failed to send dump request");
exit(1);
}
if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
fprintf(stderr, "Dump terminated\n");
exit(1);
}
}
return 0;
}
int do_tcp_metrics(int argc, char **argv)
{
int i;
if (argc < 1)
return tcpm_do_cmd(CMD_LIST, 0, NULL);
for (i = 0; i < ARRAY_SIZE(cmds); i++) {
if (matches(argv[0], cmds[i].name) == 0)
return tcpm_do_cmd(cmds[i].code, argc-1, argv+1);
}
if (matches(argv[0], "help") == 0)
usage();
fprintf(stderr, "Command \"%s\" is unknown, try \"ip tcp_metrics help\".\n",
*argv);
exit(-1);
}