]>
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 | { | |
8589eb4e MC |
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"); | |
ea63a69b JA |
36 | exit(-1); |
37 | } | |
38 | ||
39 | /* netlink socket */ | |
40 | static struct rtnl_handle grth = { .fd = -1 }; | |
41 | static int genl_family = -1; | |
74498126 | 42 | static const double usec_per_sec = 1000000.; |
ea63a69b JA |
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 | ||
8a61d896 SH |
52 | static const struct { |
53 | const char *name; | |
ea63a69b JA |
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 | ||
8a61d896 | 64 | static const char *metric_name[TCP_METRIC_MAX + 1] = { |
ea63a69b JA |
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 | ||
8a61d896 | 72 | static struct { |
ea63a69b JA |
73 | int flushed; |
74 | char *flushb; | |
75 | int flushp; | |
76 | int flushe; | |
77 | int cmd; | |
54b237a0 | 78 | inet_prefix daddr; |
c3304904 | 79 | inet_prefix saddr; |
ea63a69b JA |
80 | } f; |
81 | ||
82 | static 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 | ||
74498126 SH |
92 | static 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); | |
4db2ff0d | 143 | print_uint(PRINT_FP, NULL, "%u", val); |
74498126 SH |
144 | } |
145 | ||
146 | if (rtt) { | |
147 | print_float(PRINT_JSON, "rtt", NULL, | |
148 | (double)rtt / usec_per_sec); | |
4db2ff0d | 149 | print_u64(PRINT_FP, NULL, |
74498126 SH |
150 | " rtt %luus", rtt); |
151 | } | |
152 | if (rttvar) { | |
153 | print_float(PRINT_JSON, "rttvar", NULL, | |
154 | (double) rttvar / usec_per_sec); | |
4db2ff0d | 155 | print_u64(PRINT_FP, NULL, |
74498126 SH |
156 | " rttvar %luus", rttvar); |
157 | } | |
158 | } | |
159 | ||
cd554f2c | 160 | static int process_msg(struct nlmsghdr *n, void *arg) |
ea63a69b JA |
161 | { |
162 | FILE *fp = (FILE *) arg; | |
163 | struct genlmsghdr *ghdr; | |
164 | struct rtattr *attrs[TCP_METRICS_ATTR_MAX + 1], *a; | |
74498126 | 165 | const char *h; |
ea63a69b | 166 | int len = n->nlmsg_len; |
114aa720 | 167 | inet_prefix daddr, saddr; |
74498126 | 168 | int atype, stype; |
ea63a69b JA |
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 | ||
08401220 | 184 | if (attrs[TCP_METRICS_ATTR_ADDR_IPV4]) { |
54b237a0 | 185 | if (f.daddr.family && f.daddr.family != AF_INET) |
ea63a69b | 186 | return 0; |
08401220 | 187 | a = attrs[TCP_METRICS_ATTR_ADDR_IPV4]; |
228f2e97 | 188 | daddr.family = AF_INET; |
ea63a69b | 189 | atype = TCP_METRICS_ATTR_ADDR_IPV4; |
08401220 JS |
190 | } else if (attrs[TCP_METRICS_ATTR_ADDR_IPV6]) { |
191 | if (f.daddr.family && f.daddr.family != AF_INET6) | |
ea63a69b | 192 | return 0; |
08401220 | 193 | a = attrs[TCP_METRICS_ATTR_ADDR_IPV6]; |
228f2e97 | 194 | daddr.family = AF_INET6; |
08401220 | 195 | atype = TCP_METRICS_ATTR_ADDR_IPV6; |
08401220 JS |
196 | } else { |
197 | return 0; | |
ea63a69b JA |
198 | } |
199 | ||
228f2e97 SP |
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 | ||
08401220 | 207 | if (attrs[TCP_METRICS_ATTR_SADDR_IPV4]) { |
114aa720 CP |
208 | if (f.saddr.family && f.saddr.family != AF_INET) |
209 | return 0; | |
08401220 | 210 | a = attrs[TCP_METRICS_ATTR_SADDR_IPV4]; |
228f2e97 | 211 | saddr.family = AF_INET; |
114aa720 | 212 | stype = TCP_METRICS_ATTR_SADDR_IPV4; |
08401220 JS |
213 | } else if (attrs[TCP_METRICS_ATTR_SADDR_IPV6]) { |
214 | if (f.saddr.family && f.saddr.family != AF_INET6) | |
215 | return 0; | |
114aa720 | 216 | a = attrs[TCP_METRICS_ATTR_SADDR_IPV6]; |
228f2e97 | 217 | saddr.family = AF_INET6; |
08401220 | 218 | stype = TCP_METRICS_ATTR_SADDR_IPV6; |
228f2e97 SP |
219 | } else { |
220 | saddr.family = AF_UNSPEC; | |
221 | stype = 0; | |
114aa720 CP |
222 | } |
223 | ||
228f2e97 SP |
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 | } | |
ea63a69b JA |
233 | |
234 | if (f.flushb) { | |
235 | struct nlmsghdr *fn; | |
56f5daac | 236 | |
ea63a69b JA |
237 | TCPM_REQUEST(req2, 128, TCP_METRICS_CMD_DEL, NLM_F_REQUEST); |
238 | ||
228f2e97 | 239 | addattr_l(&req2.n, sizeof(req2), atype, daddr.data, |
54b237a0 | 240 | daddr.bytelen); |
228f2e97 SP |
241 | if (saddr.family) |
242 | addattr_l(&req2.n, sizeof(req2), stype, saddr.data, | |
c3304904 | 243 | saddr.bytelen); |
ea63a69b JA |
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 | ||
74498126 | 258 | open_json_object(NULL); |
ea63a69b | 259 | if (f.cmd & (CMD_DEL | CMD_FLUSH)) |
74498126 | 260 | print_bool(PRINT_ANY, "deleted", "Deleted ", true); |
ea63a69b | 261 | |
74498126 SH |
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); | |
ea63a69b JA |
266 | |
267 | a = attrs[TCP_METRICS_ATTR_AGE]; | |
268 | if (a) { | |
74498126 SH |
269 | __u64 val = rta_getattr_u64(a); |
270 | double age = val / 1000.; | |
ea63a69b | 271 | |
74498126 SH |
272 | print_float(PRINT_ANY, "age", |
273 | " age %.03fsec", age); | |
ea63a69b JA |
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; | |
74498126 | 280 | char tw_ts[64]; |
ea63a69b JA |
281 | |
282 | a = attrs[TCP_METRICS_ATTR_TW_TSVAL]; | |
283 | tsval = a ? rta_getattr_u32(a) : 0; | |
74498126 SH |
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); | |
ea63a69b JA |
288 | } |
289 | ||
74498126 SH |
290 | if (attrs[TCP_METRICS_ATTR_VALS]) |
291 | print_tcp_metrics(attrs[TCP_METRICS_ATTR_VALS]); | |
ea63a69b JA |
292 | |
293 | a = attrs[TCP_METRICS_ATTR_FOPEN_MSS]; | |
74498126 SH |
294 | if (a) { |
295 | print_uint(PRINT_ANY, "fopen_miss", " fo_mss %u", | |
296 | rta_getattr_u16(a)); | |
297 | } | |
ea63a69b JA |
298 | |
299 | a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROPS]; | |
300 | if (a) { | |
301 | __u16 syn_loss = rta_getattr_u16(a); | |
74498126 | 302 | double ts; |
ea63a69b JA |
303 | |
304 | a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROP_TS]; | |
305 | ts = a ? rta_getattr_u64(a) : 0; | |
306 | ||
74498126 SH |
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.); | |
ea63a69b JA |
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]); | |
74498126 SH |
325 | |
326 | print_string(PRINT_ANY, "fo_cookie", | |
327 | " fo_cookie %s", cookie); | |
ea63a69b JA |
328 | } |
329 | ||
228f2e97 | 330 | if (saddr.family) { |
74498126 | 331 | const char *src; |
114aa720 | 332 | |
74498126 SH |
333 | src = format_host(saddr.family, saddr.bytelen, saddr.data); |
334 | print_string(PRINT_ANY, "source", | |
335 | " source %s", src); | |
336 | } | |
ea63a69b | 337 | |
74498126 SH |
338 | print_string(PRINT_FP, NULL, "\n", ""); |
339 | close_json_object(); | |
ea63a69b JA |
340 | fflush(fp); |
341 | return 0; | |
342 | } | |
343 | ||
344 | static int tcpm_do_cmd(int cmd, int argc, char **argv) | |
345 | { | |
346 | TCPM_REQUEST(req, 1024, TCP_METRICS_CMD_GET, NLM_F_REQUEST); | |
86bf43c7 | 347 | struct nlmsghdr *answer; |
c3304904 | 348 | int atype = -1, stype = -1; |
ea63a69b JA |
349 | int ack; |
350 | ||
351 | memset(&f, 0, sizeof(f)); | |
54b237a0 CP |
352 | f.daddr.bitlen = -1; |
353 | f.daddr.family = preferred_family; | |
c3304904 CP |
354 | f.saddr.bitlen = -1; |
355 | f.saddr.family = preferred_family; | |
ea63a69b JA |
356 | |
357 | switch (preferred_family) { | |
358 | case AF_UNSPEC: | |
359 | case AF_INET: | |
360 | case AF_INET6: | |
361 | break; | |
362 | default: | |
14645ec2 | 363 | fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family); |
ea63a69b JA |
364 | return -1; |
365 | } | |
366 | ||
367 | for (; argc > 0; argc--, argv++) { | |
c3304904 CP |
368 | if (strcmp(*argv, "src") == 0 || |
369 | strcmp(*argv, "source") == 0) { | |
370 | char *who = *argv; | |
56f5daac | 371 | |
ea63a69b | 372 | NEXT_ARG(); |
c3304904 CP |
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 | } | |
ea63a69b | 385 | |
c3304904 CP |
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"; | |
56f5daac | 393 | |
c3304904 CP |
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 | } | |
ea63a69b JA |
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 */ | |
54b237a0 | 428 | if (cmd == CMD_FLUSH && f.daddr.bitlen <= 0 && |
c3304904 | 429 | f.saddr.bitlen <= 0 && preferred_family == AF_UNSPEC) { |
ea63a69b JA |
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 | ||
2b68cb77 SD |
440 | if (genl_init_handle(&grth, TCP_METRICS_GENL_NAME, &genl_family)) |
441 | exit(1); | |
442 | req.n.nlmsg_type = genl_family; | |
ea63a69b JA |
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) | |
54b237a0 CP |
448 | addattr_l(&req.n, sizeof(req), atype, &f.daddr.data, |
449 | f.daddr.bytelen); | |
c3304904 CP |
450 | if (stype >= 0) |
451 | addattr_l(&req.n, sizeof(req), stype, &f.saddr.data, | |
452 | f.saddr.bytelen); | |
ea63a69b JA |
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) { | |
86bf43c7 | 499 | if (rtnl_talk(&grth, &req.n, NULL) < 0) |
ea63a69b JA |
500 | return -2; |
501 | } else if (atype >= 0) { | |
86bf43c7 | 502 | if (rtnl_talk(&grth, &req.n, &answer) < 0) |
ea63a69b | 503 | return -2; |
cd554f2c | 504 | if (process_msg(answer, stdout) < 0) { |
ea63a69b JA |
505 | fprintf(stderr, "Dump terminated\n"); |
506 | exit(1); | |
507 | } | |
86bf43c7 | 508 | free(answer); |
ea63a69b JA |
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 | ||
74498126 | 516 | new_json_obj(json); |
ea63a69b JA |
517 | if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) { |
518 | fprintf(stderr, "Dump terminated\n"); | |
519 | exit(1); | |
520 | } | |
74498126 | 521 | delete_json_obj(); |
ea63a69b JA |
522 | } |
523 | return 0; | |
524 | } | |
525 | ||
526 | int 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 | ||
56f5daac SH |
539 | fprintf(stderr, "Command \"%s\" is unknown, try \"ip tcp_metrics help\".\n", |
540 | *argv); | |
ea63a69b JA |
541 | exit(-1); |
542 | } |