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