expose traffic stats to 'tinc info ___' and 'tinc dump nodes'
[tinc] / src / info.c
1 /*
2     info.c -- Show information about a node, subnet or address
3     Copyright (C) 2012-2017 Guus Sliepen <guus@tinc-vpn.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "system.h"
21
22 #include "control_common.h"
23 #include "list.h"
24 #include "subnet.h"
25 #include "tincctl.h"
26 #include "info.h"
27 #include "utils.h"
28 #include "xalloc.h"
29
30 void logger(int level, int priority, const char *format, ...) {
31         va_list ap;
32         va_start(ap, format);
33         vfprintf(stderr, format, ap);
34         va_end(ap);
35         fputc('\n', stderr);
36 }
37
38 char *strip_weight(char *netstr) {
39         int len = strlen(netstr);
40
41         if(len >= 3 && !strcmp(netstr + len - 3, "#10")) {
42                 netstr[len - 3] = 0;
43         }
44
45         return netstr;
46 }
47
48 static int info_node(int fd, const char *item) {
49         // Check the list of nodes
50         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_NODES, item);
51
52         bool found = false;
53         char line[4096];
54
55         char node[4096];
56         char id[4096];
57         char from[4096];
58         char to[4096];
59         char subnet[4096];
60         char host[4096];
61         char port[4096];
62         char via[4096];
63         char nexthop[4096];
64         int code, req, cipher, digest, maclength, compression, distance;
65         short int pmtu, minmtu, maxmtu;
66         unsigned int options;
67         union {
68                 node_status_t bits;
69                 uint32_t raw;
70         } status_union;
71         node_status_t status;
72         long int last_state_change;
73         long int udp_ping_rtt;
74         uint64_t in_packets, in_bytes, out_packets, out_bytes;
75
76         while(recvline(fd, line, sizeof(line))) {
77                 int n = sscanf(line, "%d %d %4095s %4095s %4095s port %4095s %d %d %d %d %x %"PRIx32" %4095s %4095s %d %hd %hd %hd %ld %ld %lu %lu %lu %lu", &code, &req, node, id, host, port, &cipher, &digest, &maclength, &compression, &options, &status_union.raw, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change, &udp_ping_rtt, &in_packets, &in_bytes, &out_packets, &out_bytes);
78
79                 if(n == 2) {
80                         break;
81                 }
82
83                 if(n != 24) {
84                         fprintf(stderr, "Unable to parse node dump from tincd.\n");
85                         return 1;
86                 }
87
88                 if(!strcmp(node, item)) {
89                         found = true;
90                         break;
91                 }
92         }
93
94         if(!found) {
95                 fprintf(stderr, "Unknown node %s.\n", item);
96                 return 1;
97         }
98
99         while(recvline(fd, line, sizeof(line))) {
100                 if(sscanf(line, "%d %d %4095s", &code, &req, node) == 2) {
101                         break;
102                 }
103         }
104
105         printf("Node:         %s\n", item);
106         printf("Node ID:      %s\n", id);
107         printf("Address:      %s port %s\n", host, port);
108
109         char timestr[32] = "never";
110         time_t lsc_time = last_state_change;
111
112         if(last_state_change) {
113                 strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&lsc_time));
114         }
115
116         status = status_union.bits;
117
118         if(status.reachable) {
119                 printf("Online since: %s\n", timestr);
120         } else {
121                 printf("Last seen:    %s\n", timestr);
122         }
123
124         printf("Status:      ");
125
126         if(status.validkey) {
127                 printf(" validkey");
128         }
129
130         if(status.visited) {
131                 printf(" visited");
132         }
133
134         if(status.reachable) {
135                 printf(" reachable");
136         }
137
138         if(status.indirect) {
139                 printf(" indirect");
140         }
141
142         if(status.sptps) {
143                 printf(" sptps");
144         }
145
146         if(status.udp_confirmed) {
147                 printf(" udp_confirmed");
148                 if(udp_ping_rtt != -1)
149                         printf(" (rtt %ld.%03ld)", udp_ping_rtt/1000, udp_ping_rtt%1000);
150         }
151
152         printf("\n");
153
154         printf("Options:     ");
155
156         if(options & OPTION_INDIRECT) {
157                 printf(" indirect");
158         }
159
160         if(options & OPTION_TCPONLY) {
161                 printf(" tcponly");
162         }
163
164         if(options & OPTION_PMTU_DISCOVERY) {
165                 printf(" pmtu_discovery");
166         }
167
168         if(options & OPTION_CLAMP_MSS) {
169                 printf(" clamp_mss");
170         }
171
172         printf("\n");
173         printf("Protocol:     %d.%d\n", PROT_MAJOR, OPTION_VERSION(options));
174         printf("Reachability: ");
175
176         if(!strcmp(host, "MYSELF")) {
177                 printf("can reach itself\n");
178         } else if(!status.reachable) {
179                 printf("unreachable\n");
180         } else if(strcmp(via, item)) {
181                 printf("indirectly via %s\n", via);
182         } else if(!status.validkey) {
183                 printf("unknown\n");
184         } else if(minmtu > 0) {
185                 printf("directly with UDP\nPMTU:         %d\n", pmtu);
186         } else if(!strcmp(nexthop, item)) {
187                 printf("directly with TCP\n");
188         } else {
189                 printf("none, forwarded via %s\n", nexthop);
190         }
191
192         printf("RX:          %lu packets  %lu bytes\n", in_packets, in_bytes);
193         printf("TX:          %lu packets  %lu bytes\n", out_packets, out_bytes);
194
195         // List edges
196         printf("Edges:       ");
197         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_EDGES, item);
198
199         while(recvline(fd, line, sizeof(line))) {
200                 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, from, to);
201
202                 if(n == 2) {
203                         break;
204                 }
205
206                 if(n != 4) {
207                         fprintf(stderr, "Unable to parse edge dump from tincd.\n%s\n", line);
208                         return 1;
209                 }
210
211                 if(!strcmp(from, item)) {
212                         printf(" %s", to);
213                 }
214         }
215
216         printf("\n");
217
218         // List subnets
219         printf("Subnets:     ");
220         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item);
221
222         while(recvline(fd, line, sizeof(line))) {
223                 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, subnet, from);
224
225                 if(n == 2) {
226                         break;
227                 }
228
229                 if(n != 4) {
230                         fprintf(stderr, "Unable to parse subnet dump from tincd.\n");
231                         return 1;
232                 }
233
234                 if(!strcmp(from, item)) {
235                         printf(" %s", strip_weight(subnet));
236                 }
237         }
238
239         printf("\n");
240
241         return 0;
242 }
243
244 static int info_subnet(int fd, const char *item) {
245         subnet_t subnet, find;
246
247         if(!str2net(&find, item)) {
248                 fprintf(stderr, "Could not parse subnet or address '%s'.\n", item);
249                 return 1;
250         }
251
252         bool address = !strchr(item, '/');
253         bool weight = strchr(item, '#');
254         bool found = false;
255
256         char line[4096];
257         char netstr[4096];
258         char owner[4096];
259
260         int code, req;
261
262         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item);
263
264         while(recvline(fd, line, sizeof(line))) {
265                 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, netstr, owner);
266
267                 if(n == 2) {
268                         break;
269                 }
270
271                 if(n != 4 || !str2net(&subnet, netstr)) {
272                         fprintf(stderr, "Unable to parse subnet dump from tincd.\n");
273                         return 1;
274                 }
275
276                 if(find.type != subnet.type) {
277                         continue;
278                 }
279
280                 if(weight) {
281                         if(find.weight != subnet.weight) {
282                                 continue;
283                         }
284                 }
285
286                 if(find.type == SUBNET_IPV4) {
287                         if(address) {
288                                 if(maskcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, subnet.net.ipv4.prefixlength)) {
289                                         continue;
290                                 }
291                         } else {
292                                 if(find.net.ipv4.prefixlength != subnet.net.ipv4.prefixlength) {
293                                         continue;
294                                 }
295
296                                 if(memcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, sizeof(subnet.net.ipv4))) {
297                                         continue;
298                                 }
299                         }
300                 } else if(find.type == SUBNET_IPV6) {
301                         if(address) {
302                                 if(maskcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, subnet.net.ipv6.prefixlength)) {
303                                         continue;
304                                 }
305                         } else {
306                                 if(find.net.ipv6.prefixlength != subnet.net.ipv6.prefixlength) {
307                                         continue;
308                                 }
309
310                                 if(memcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, sizeof(subnet.net.ipv6))) {
311                                         continue;
312                                 }
313                         }
314                 }
315
316                 if(find.type == SUBNET_MAC) {
317                         if(memcmp(&find.net.mac.address, &subnet.net.mac.address, sizeof(subnet.net.mac))) {
318                                 continue;
319                         }
320                 }
321
322                 found = true;
323                 printf("Subnet: %s\n", strip_weight(netstr));
324                 printf("Owner:  %s\n", owner);
325         }
326
327         if(!found) {
328                 if(address) {
329                         fprintf(stderr, "Unknown address %s.\n", item);
330                 } else {
331                         fprintf(stderr, "Unknown subnet %s.\n", item);
332                 }
333
334                 return 1;
335         }
336
337         return 0;
338 }
339
340 int info(int fd, const char *item) {
341         if(check_id(item)) {
342                 return info_node(fd, item);
343         }
344
345         if(strchr(item, '.') || strchr(item, ':')) {
346                 return info_subnet(fd, item);
347         }
348
349         fprintf(stderr, "Argument is not a node name, subnet or address.\n");
350         return 1;
351 }