iproute2/ip/tcp_metrics.c
<<
>>
Prefs
   1/*
   2 * tcp_metrics.c        "ip tcp_metrics/tcpmetrics"
   3 *
   4 *              This program is free software; you can redistribute it and/or
   5 *              modify it under the terms of the GNU General Public License
   6 *              version 2 as published by the Free Software Foundation;
   7 *
   8 * Authors:     Julian Anastasov <ja@ssi.bg>, August 2012
   9 */
  10
  11#include <stdio.h>
  12#include <stdlib.h>
  13#include <string.h>
  14#include <unistd.h>
  15#include <errno.h>
  16#include <sys/types.h>
  17#include <sys/socket.h>
  18#include <arpa/inet.h>
  19#include <sys/ioctl.h>
  20#include <linux/if.h>
  21
  22#include <linux/genetlink.h>
  23#include <linux/tcp_metrics.h>
  24
  25#include "utils.h"
  26#include "ip_common.h"
  27#include "libgenl.h"
  28
  29static void usage(void)
  30{
  31        fprintf(stderr,
  32                "Usage: ip tcp_metrics/tcpmetrics { COMMAND | help }\n"
  33                "       ip tcp_metrics { show | flush } SELECTOR\n"
  34                "       ip tcp_metrics delete [ address ] ADDRESS\n"
  35                "SELECTOR := [ [ address ] PREFIX ]\n");
  36        exit(-1);
  37}
  38
  39/* netlink socket */
  40static struct rtnl_handle grth = { .fd = -1 };
  41static int genl_family = -1;
  42static const double usec_per_sec = 1000000.;
  43
  44#define TCPM_REQUEST(_req, _bufsiz, _cmd, _flags) \
  45        GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
  46                     TCP_METRICS_GENL_VERSION, _cmd, _flags)
  47
  48#define CMD_LIST        0x0001  /* list, lst, show              */
  49#define CMD_DEL         0x0002  /* delete, remove               */
  50#define CMD_FLUSH       0x0004  /* flush                        */
  51
  52static const struct {
  53        const char *name;
  54        int     code;
  55} cmds[] = {
  56        {       "list",         CMD_LIST        },
  57        {       "lst",          CMD_LIST        },
  58        {       "show",         CMD_LIST        },
  59        {       "delete",       CMD_DEL         },
  60        {       "remove",       CMD_DEL         },
  61        {       "flush",        CMD_FLUSH       },
  62};
  63
  64static const char *metric_name[TCP_METRIC_MAX + 1] = {
  65        [TCP_METRIC_RTT]                = "rtt",
  66        [TCP_METRIC_RTTVAR]             = "rttvar",
  67        [TCP_METRIC_SSTHRESH]           = "ssthresh",
  68        [TCP_METRIC_CWND]               = "cwnd",
  69        [TCP_METRIC_REORDERING]         = "reordering",
  70};
  71
  72static struct {
  73        int flushed;
  74        char *flushb;
  75        int flushp;
  76        int flushe;
  77        int cmd;
  78        inet_prefix daddr;
  79        inet_prefix saddr;
  80} f;
  81
  82static int flush_update(void)
  83{
  84        if (rtnl_send_check(&grth, f.flushb, f.flushp) < 0) {
  85                perror("Failed to send flush request\n");
  86                return -1;
  87        }
  88        f.flushp = 0;
  89        return 0;
  90}
  91
  92static void print_tcp_metrics(struct rtattr *a)
  93{
  94        struct rtattr *m[TCP_METRIC_MAX + 1 + 1];
  95        unsigned long rtt = 0, rttvar = 0;
  96        int i;
  97
  98        parse_rtattr_nested(m, TCP_METRIC_MAX + 1, a);
  99
 100        for (i = 0; i < TCP_METRIC_MAX + 1; i++) {
 101                const char *name;
 102                __u32 val;
 103                SPRINT_BUF(b1);
 104
 105                a = m[i + 1];
 106                if (!a)
 107                        continue;
 108
 109                val = rta_getattr_u32(a);
 110
 111                switch (i) {
 112                case TCP_METRIC_RTT:
 113                        if (!rtt)
 114                                rtt = (val * 1000UL) >> 3;
 115                        continue;
 116                case TCP_METRIC_RTTVAR:
 117                        if (!rttvar)
 118                                rttvar = (val * 1000UL) >> 2;
 119                        continue;
 120                case TCP_METRIC_RTT_US:
 121                        rtt = val >> 3;
 122                        continue;
 123
 124                case TCP_METRIC_RTTVAR_US:
 125                        rttvar = val >> 2;
 126                        continue;
 127
 128                case TCP_METRIC_SSTHRESH:
 129                case TCP_METRIC_CWND:
 130                case TCP_METRIC_REORDERING:
 131                        name = metric_name[i];
 132                        break;
 133
 134                default:
 135                        snprintf(b1, sizeof(b1),
 136                                 " metric_%d ", i);
 137                        name = b1;
 138                }
 139
 140
 141                print_uint(PRINT_JSON, name, NULL, val);
 142                print_string(PRINT_FP, NULL, " %s ", name);
 143                print_uint(PRINT_FP, NULL, "%u", val);
 144        }
 145
 146        if (rtt) {
 147                print_float(PRINT_JSON, "rtt", NULL,
 148                            (double)rtt / usec_per_sec);
 149                print_u64(PRINT_FP, NULL,
 150                           " rtt %luus", rtt);
 151        }
 152        if (rttvar) {
 153                print_float(PRINT_JSON, "rttvar", NULL,
 154                            (double) rttvar / usec_per_sec);
 155                print_u64(PRINT_FP, NULL,
 156                           " rttvar %luus", rttvar);
 157        }
 158}
 159
 160static int process_msg(struct nlmsghdr *n, void *arg)
 161{
 162        FILE *fp = (FILE *) arg;
 163        struct genlmsghdr *ghdr;
 164        struct rtattr *attrs[TCP_METRICS_ATTR_MAX + 1], *a;
 165        const char *h;
 166        int len = n->nlmsg_len;
 167        inet_prefix daddr, saddr;
 168        int atype, stype;
 169
 170        if (n->nlmsg_type != genl_family)
 171                return -1;
 172
 173        len -= NLMSG_LENGTH(GENL_HDRLEN);
 174        if (len < 0)
 175                return -1;
 176
 177        ghdr = NLMSG_DATA(n);
 178        if (ghdr->cmd != TCP_METRICS_CMD_GET)
 179                return 0;
 180
 181        parse_rtattr(attrs, TCP_METRICS_ATTR_MAX, (void *) ghdr + GENL_HDRLEN,
 182                     len);
 183
 184        if (attrs[TCP_METRICS_ATTR_ADDR_IPV4]) {
 185                if (f.daddr.family && f.daddr.family != AF_INET)
 186                        return 0;
 187                a = attrs[TCP_METRICS_ATTR_ADDR_IPV4];
 188                daddr.family = AF_INET;
 189                atype = TCP_METRICS_ATTR_ADDR_IPV4;
 190        } else if (attrs[TCP_METRICS_ATTR_ADDR_IPV6]) {
 191                if (f.daddr.family && f.daddr.family != AF_INET6)
 192                        return 0;
 193                a = attrs[TCP_METRICS_ATTR_ADDR_IPV6];
 194                daddr.family = AF_INET6;
 195                atype = TCP_METRICS_ATTR_ADDR_IPV6;
 196        } else {
 197                return 0;
 198        }
 199
 200        if (get_addr_rta(&daddr, a, daddr.family))
 201                return 0;
 202
 203        if (f.daddr.family && f.daddr.bitlen >= 0 &&
 204            inet_addr_match(&daddr, &f.daddr, f.daddr.bitlen))
 205                return 0;
 206
 207        if (attrs[TCP_METRICS_ATTR_SADDR_IPV4]) {
 208                if (f.saddr.family && f.saddr.family != AF_INET)
 209                        return 0;
 210                a = attrs[TCP_METRICS_ATTR_SADDR_IPV4];
 211                saddr.family = AF_INET;
 212                stype = TCP_METRICS_ATTR_SADDR_IPV4;
 213        } else if (attrs[TCP_METRICS_ATTR_SADDR_IPV6]) {
 214                if (f.saddr.family && f.saddr.family != AF_INET6)
 215                        return 0;
 216                a = attrs[TCP_METRICS_ATTR_SADDR_IPV6];
 217                saddr.family = AF_INET6;
 218                stype = TCP_METRICS_ATTR_SADDR_IPV6;
 219        } else {
 220                saddr.family = AF_UNSPEC;
 221                stype = 0;
 222        }
 223
 224        /* Only get/check for the source-address if the kernel supports it. */
 225        if (saddr.family) {
 226                if (get_addr_rta(&saddr, a, saddr.family))
 227                        return 0;
 228
 229                if (f.saddr.family && f.saddr.bitlen >= 0 &&
 230                    inet_addr_match(&saddr, &f.saddr, f.saddr.bitlen))
 231                        return 0;
 232        }
 233
 234        if (f.flushb) {
 235                struct nlmsghdr *fn;
 236
 237                TCPM_REQUEST(req2, 128, TCP_METRICS_CMD_DEL, NLM_F_REQUEST);
 238
 239                addattr_l(&req2.n, sizeof(req2), atype, daddr.data,
 240                          daddr.bytelen);
 241                if (saddr.family)
 242                        addattr_l(&req2.n, sizeof(req2), stype, saddr.data,
 243                                  saddr.bytelen);
 244
 245                if (NLMSG_ALIGN(f.flushp) + req2.n.nlmsg_len > f.flushe) {
 246                        if (flush_update())
 247                                return -1;
 248                }
 249                fn = (struct nlmsghdr *) (f.flushb + NLMSG_ALIGN(f.flushp));
 250                memcpy(fn, &req2.n, req2.n.nlmsg_len);
 251                fn->nlmsg_seq = ++grth.seq;
 252                f.flushp = (((char *) fn) + req2.n.nlmsg_len) - f.flushb;
 253                f.flushed++;
 254                if (show_stats < 2)
 255                        return 0;
 256        }
 257
 258        open_json_object(NULL);
 259        if (f.cmd & (CMD_DEL | CMD_FLUSH))
 260                print_bool(PRINT_ANY, "deleted", "Deleted ", true);
 261
 262        h = format_host(daddr.family, daddr.bytelen, daddr.data);
 263        print_color_string(PRINT_ANY,
 264                           ifa_family_color(daddr.family),
 265                           "dst", "%s", h);
 266
 267        a = attrs[TCP_METRICS_ATTR_AGE];
 268        if (a) {
 269                __u64 val = rta_getattr_u64(a);
 270                double age = val / 1000.;
 271
 272                print_float(PRINT_ANY, "age",
 273                             " age %.03fsec", age);
 274        }
 275
 276        a = attrs[TCP_METRICS_ATTR_TW_TS_STAMP];
 277        if (a) {
 278                __s32 val = (__s32) rta_getattr_u32(a);
 279                __u32 tsval;
 280                char tw_ts[64];
 281
 282                a = attrs[TCP_METRICS_ATTR_TW_TSVAL];
 283                tsval = a ? rta_getattr_u32(a) : 0;
 284                snprintf(tw_ts, sizeof(tw_ts),
 285                         "%u/%d", tsval, val);
 286                print_string(PRINT_ANY, "tw_ts_stamp",
 287                     " tw_ts %s ago", tw_ts);
 288        }
 289
 290        if (attrs[TCP_METRICS_ATTR_VALS])
 291                print_tcp_metrics(attrs[TCP_METRICS_ATTR_VALS]);
 292
 293        a = attrs[TCP_METRICS_ATTR_FOPEN_MSS];
 294        if (a) {
 295                print_uint(PRINT_ANY, "fopen_miss", " fo_mss %u",
 296                           rta_getattr_u16(a));
 297        }
 298
 299        a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROPS];
 300        if (a) {
 301                __u16 syn_loss = rta_getattr_u16(a);
 302                double ts;
 303
 304                a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROP_TS];
 305                ts = a ? rta_getattr_u64(a) : 0;
 306
 307                print_uint(PRINT_ANY, "fopen_syn_drops",
 308                           " fo_syn_drops %u", syn_loss);
 309                print_float(PRINT_ANY, "fopen_syn_drop_ts",
 310                             "/%.03fusec ago",
 311                             ts / 1000000.);
 312        }
 313
 314        a = attrs[TCP_METRICS_ATTR_FOPEN_COOKIE];
 315        if (a) {
 316                char cookie[32 + 1];
 317                unsigned char *ptr = RTA_DATA(a);
 318                int i, max = RTA_PAYLOAD(a);
 319
 320                if (max > 16)
 321                        max = 16;
 322                cookie[0] = 0;
 323                for (i = 0; i < max; i++)
 324                        sprintf(cookie + i + i, "%02x", ptr[i]);
 325
 326                print_string(PRINT_ANY, "fo_cookie",
 327                             " fo_cookie %s", cookie);
 328        }
 329
 330        if (saddr.family) {
 331                const char *src;
 332
 333                src = format_host(saddr.family, saddr.bytelen, saddr.data);
 334                print_string(PRINT_ANY, "source",
 335                             " source %s", src);
 336        }
 337
 338        print_string(PRINT_FP, NULL, "\n", "");
 339        close_json_object();
 340        fflush(fp);
 341        return 0;
 342}
 343
 344static int tcpm_do_cmd(int cmd, int argc, char **argv)
 345{
 346        TCPM_REQUEST(req, 1024, TCP_METRICS_CMD_GET, NLM_F_REQUEST);
 347        struct nlmsghdr *answer;
 348        int atype = -1, stype = -1;
 349        int ack;
 350
 351        memset(&f, 0, sizeof(f));
 352        f.daddr.bitlen = -1;
 353        f.daddr.family = preferred_family;
 354        f.saddr.bitlen = -1;
 355        f.saddr.family = preferred_family;
 356
 357        switch (preferred_family) {
 358        case AF_UNSPEC:
 359        case AF_INET:
 360        case AF_INET6:
 361                break;
 362        default:
 363                fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
 364                return -1;
 365        }
 366
 367        for (; argc > 0; argc--, argv++) {
 368                if (strcmp(*argv, "src") == 0 ||
 369                    strcmp(*argv, "source") == 0) {
 370                        char *who = *argv;
 371
 372                        NEXT_ARG();
 373                        if (matches(*argv, "help") == 0)
 374                                usage();
 375                        if (f.saddr.bitlen >= 0)
 376                                duparg2(who, *argv);
 377
 378                        get_prefix(&f.saddr, *argv, preferred_family);
 379                        if (f.saddr.bytelen && f.saddr.bytelen * 8 == f.saddr.bitlen) {
 380                                if (f.saddr.family == AF_INET)
 381                                        stype = TCP_METRICS_ATTR_SADDR_IPV4;
 382                                else if (f.saddr.family == AF_INET6)
 383                                        stype = TCP_METRICS_ATTR_SADDR_IPV6;
 384                        }
 385
 386                        if (stype < 0) {
 387                                fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
 388                                        *argv);
 389                                return -1;
 390                        }
 391                } else {
 392                        char *who = "address";
 393
 394                        if (strcmp(*argv, "addr") == 0 ||
 395                            strcmp(*argv, "address") == 0) {
 396                                who = *argv;
 397                                NEXT_ARG();
 398                        }
 399                        if (matches(*argv, "help") == 0)
 400                                usage();
 401                        if (f.daddr.bitlen >= 0)
 402                                duparg2(who, *argv);
 403
 404                        get_prefix(&f.daddr, *argv, preferred_family);
 405                        if (f.daddr.bytelen && f.daddr.bytelen * 8 == f.daddr.bitlen) {
 406                                if (f.daddr.family == AF_INET)
 407                                        atype = TCP_METRICS_ATTR_ADDR_IPV4;
 408                                else if (f.daddr.family == AF_INET6)
 409                                        atype = TCP_METRICS_ATTR_ADDR_IPV6;
 410                        }
 411                        if ((CMD_DEL & cmd) && atype < 0) {
 412                                fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
 413                                        *argv);
 414                                return -1;
 415                        }
 416                }
 417                argc--; argv++;
 418        }
 419
 420        if (cmd == CMD_DEL && atype < 0)
 421                missarg("address");
 422
 423        /* flush for exact address ? Single del */
 424        if (cmd == CMD_FLUSH && atype >= 0)
 425                cmd = CMD_DEL;
 426
 427        /* flush for all addresses ? Single del without address */
 428        if (cmd == CMD_FLUSH && f.daddr.bitlen <= 0 &&
 429            f.saddr.bitlen <= 0 && preferred_family == AF_UNSPEC) {
 430                cmd = CMD_DEL;
 431                req.g.cmd = TCP_METRICS_CMD_DEL;
 432                ack = 1;
 433        } else if (cmd == CMD_DEL) {
 434                req.g.cmd = TCP_METRICS_CMD_DEL;
 435                ack = 1;
 436        } else {        /* CMD_FLUSH, CMD_LIST */
 437                ack = 0;
 438        }
 439
 440        if (genl_init_handle(&grth, TCP_METRICS_GENL_NAME, &genl_family))
 441                exit(1);
 442        req.n.nlmsg_type = genl_family;
 443
 444        if (!(cmd & CMD_FLUSH) && (atype >= 0 || (cmd & CMD_DEL))) {
 445                if (ack)
 446                        req.n.nlmsg_flags |= NLM_F_ACK;
 447                if (atype >= 0)
 448                        addattr_l(&req.n, sizeof(req), atype, &f.daddr.data,
 449                                  f.daddr.bytelen);
 450                if (stype >= 0)
 451                        addattr_l(&req.n, sizeof(req), stype, &f.saddr.data,
 452                                  f.saddr.bytelen);
 453        } else {
 454                req.n.nlmsg_flags |= NLM_F_DUMP;
 455        }
 456
 457        f.cmd = cmd;
 458        if (cmd & CMD_FLUSH) {
 459                int round = 0;
 460                char flushb[4096-512];
 461
 462                f.flushb = flushb;
 463                f.flushp = 0;
 464                f.flushe = sizeof(flushb);
 465
 466                for (;;) {
 467                        req.n.nlmsg_seq = grth.dump = ++grth.seq;
 468                        if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
 469                                perror("Failed to send flush request");
 470                                exit(1);
 471                        }
 472                        f.flushed = 0;
 473                        if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
 474                                fprintf(stderr, "Flush terminated\n");
 475                                exit(1);
 476                        }
 477                        if (f.flushed == 0) {
 478                                if (round == 0) {
 479                                        fprintf(stderr, "Nothing to flush.\n");
 480                                } else if (show_stats)
 481                                        printf("*** Flush is complete after %d round%s ***\n",
 482                                               round, round > 1 ? "s" : "");
 483                                fflush(stdout);
 484                                return 0;
 485                        }
 486                        round++;
 487                        if (flush_update() < 0)
 488                                exit(1);
 489                        if (show_stats) {
 490                                printf("\n*** Round %d, deleting %d entries ***\n",
 491                                       round, f.flushed);
 492                                fflush(stdout);
 493                        }
 494                }
 495                return 0;
 496        }
 497
 498        if (ack) {
 499                if (rtnl_talk(&grth, &req.n, NULL) < 0)
 500                        return -2;
 501        } else if (atype >= 0) {
 502                if (rtnl_talk(&grth, &req.n, &answer) < 0)
 503                        return -2;
 504                if (process_msg(answer, stdout) < 0) {
 505                        fprintf(stderr, "Dump terminated\n");
 506                        exit(1);
 507                }
 508                free(answer);
 509        } else {
 510                req.n.nlmsg_seq = grth.dump = ++grth.seq;
 511                if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
 512                        perror("Failed to send dump request");
 513                        exit(1);
 514                }
 515
 516                new_json_obj(json);
 517                if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
 518                        fprintf(stderr, "Dump terminated\n");
 519                        exit(1);
 520                }
 521                delete_json_obj();
 522        }
 523        return 0;
 524}
 525
 526int do_tcp_metrics(int argc, char **argv)
 527{
 528        int i;
 529
 530        if (argc < 1)
 531                return tcpm_do_cmd(CMD_LIST, 0, NULL);
 532        for (i = 0; i < ARRAY_SIZE(cmds); i++) {
 533                if (matches(argv[0], cmds[i].name) == 0)
 534                        return tcpm_do_cmd(cmds[i].code, argc-1, argv+1);
 535        }
 536        if (matches(argv[0], "help") == 0)
 537                usage();
 538
 539        fprintf(stderr, "Command \"%s\" is unknown, try \"ip tcp_metrics help\".\n",
 540                        *argv);
 541        exit(-1);
 542}
 543