keep track of round trip times of UDP pings
[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
75         while(recvline(fd, line, sizeof(line))) {
76                 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", &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);
77
78                 if(n == 2) {
79                         break;
80                 }
81
82                 if(n != 20) {
83                         fprintf(stderr, "Unable to parse node dump from tincd.\n");
84                         return 1;
85                 }
86
87                 if(!strcmp(node, item)) {
88                         found = true;
89                         break;
90                 }
91         }
92
93         if(!found) {
94                 fprintf(stderr, "Unknown node %s.\n", item);
95                 return 1;
96         }
97
98         while(recvline(fd, line, sizeof(line))) {
99                 if(sscanf(line, "%d %d %4095s", &code, &req, node) == 2) {
100                         break;
101                 }
102         }
103
104         printf("Node:         %s\n", item);
105         printf("Node ID:      %s\n", id);
106         printf("Address:      %s port %s\n", host, port);
107
108         char timestr[32] = "never";
109         time_t lsc_time = last_state_change;
110
111         if(last_state_change) {
112                 strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&lsc_time));
113         }
114
115         status = status_union.bits;
116
117         if(status.reachable) {
118                 printf("Online since: %s\n", timestr);
119         } else {
120                 printf("Last seen:    %s\n", timestr);
121         }
122
123         printf("Status:      ");
124
125         if(status.validkey) {
126                 printf(" validkey");
127         }
128
129         if(status.visited) {
130                 printf(" visited");
131         }
132
133         if(status.reachable) {
134                 printf(" reachable");
135         }
136
137         if(status.indirect) {
138                 printf(" indirect");
139         }
140
141         if(status.sptps) {
142                 printf(" sptps");
143         }
144
145         if(status.udp_confirmed) {
146                 printf(" udp_confirmed");
147                 if(udp_ping_rtt != -1)
148                         printf(" (rtt %ld.%03ld)", udp_ping_rtt/1000, udp_ping_rtt%1000);
149         }
150
151         printf("\n");
152
153         printf("Options:     ");
154
155         if(options & OPTION_INDIRECT) {
156                 printf(" indirect");
157         }
158
159         if(options & OPTION_TCPONLY) {
160                 printf(" tcponly");
161         }
162
163         if(options & OPTION_PMTU_DISCOVERY) {
164                 printf(" pmtu_discovery");
165         }
166
167         if(options & OPTION_CLAMP_MSS) {
168                 printf(" clamp_mss");
169         }
170
171         printf("\n");
172         printf("Protocol:     %d.%d\n", PROT_MAJOR, OPTION_VERSION(options));
173         printf("Reachability: ");
174
175         if(!strcmp(host, "MYSELF")) {
176                 printf("can reach itself\n");
177         } else if(!status.reachable) {
178                 printf("unreachable\n");
179         } else if(strcmp(via, item)) {
180                 printf("indirectly via %s\n", via);
181         } else if(!status.validkey) {
182                 printf("unknown\n");
183         } else if(minmtu > 0) {
184                 printf("directly with UDP\nPMTU:         %d\n", pmtu);
185         } else if(!strcmp(nexthop, item)) {
186                 printf("directly with TCP\n");
187         } else {
188                 printf("none, forwarded via %s\n", nexthop);
189         }
190
191         // List edges
192         printf("Edges:       ");
193         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_EDGES, item);
194
195         while(recvline(fd, line, sizeof(line))) {
196                 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, from, to);
197
198                 if(n == 2) {
199                         break;
200                 }
201
202                 if(n != 4) {
203                         fprintf(stderr, "Unable to parse edge dump from tincd.\n%s\n", line);
204                         return 1;
205                 }
206
207                 if(!strcmp(from, item)) {
208                         printf(" %s", to);
209                 }
210         }
211
212         printf("\n");
213
214         // List subnets
215         printf("Subnets:     ");
216         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item);
217
218         while(recvline(fd, line, sizeof(line))) {
219                 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, subnet, from);
220
221                 if(n == 2) {
222                         break;
223                 }
224
225                 if(n != 4) {
226                         fprintf(stderr, "Unable to parse subnet dump from tincd.\n");
227                         return 1;
228                 }
229
230                 if(!strcmp(from, item)) {
231                         printf(" %s", strip_weight(subnet));
232                 }
233         }
234
235         printf("\n");
236
237         return 0;
238 }
239
240 static int info_subnet(int fd, const char *item) {
241         subnet_t subnet, find;
242
243         if(!str2net(&find, item)) {
244                 fprintf(stderr, "Could not parse subnet or address '%s'.\n", item);
245                 return 1;
246         }
247
248         bool address = !strchr(item, '/');
249         bool weight = strchr(item, '#');
250         bool found = false;
251
252         char line[4096];
253         char netstr[4096];
254         char owner[4096];
255
256         int code, req;
257
258         sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item);
259
260         while(recvline(fd, line, sizeof(line))) {
261                 int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, netstr, owner);
262
263                 if(n == 2) {
264                         break;
265                 }
266
267                 if(n != 4 || !str2net(&subnet, netstr)) {
268                         fprintf(stderr, "Unable to parse subnet dump from tincd.\n");
269                         return 1;
270                 }
271
272                 if(find.type != subnet.type) {
273                         continue;
274                 }
275
276                 if(weight) {
277                         if(find.weight != subnet.weight) {
278                                 continue;
279                         }
280                 }
281
282                 if(find.type == SUBNET_IPV4) {
283                         if(address) {
284                                 if(maskcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, subnet.net.ipv4.prefixlength)) {
285                                         continue;
286                                 }
287                         } else {
288                                 if(find.net.ipv4.prefixlength != subnet.net.ipv4.prefixlength) {
289                                         continue;
290                                 }
291
292                                 if(memcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, sizeof(subnet.net.ipv4))) {
293                                         continue;
294                                 }
295                         }
296                 } else if(find.type == SUBNET_IPV6) {
297                         if(address) {
298                                 if(maskcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, subnet.net.ipv6.prefixlength)) {
299                                         continue;
300                                 }
301                         } else {
302                                 if(find.net.ipv6.prefixlength != subnet.net.ipv6.prefixlength) {
303                                         continue;
304                                 }
305
306                                 if(memcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, sizeof(subnet.net.ipv6))) {
307                                         continue;
308                                 }
309                         }
310                 }
311
312                 if(find.type == SUBNET_MAC) {
313                         if(memcmp(&find.net.mac.address, &subnet.net.mac.address, sizeof(subnet.net.mac))) {
314                                 continue;
315                         }
316                 }
317
318                 found = true;
319                 printf("Subnet: %s\n", strip_weight(netstr));
320                 printf("Owner:  %s\n", owner);
321         }
322
323         if(!found) {
324                 if(address) {
325                         fprintf(stderr, "Unknown address %s.\n", item);
326                 } else {
327                         fprintf(stderr, "Unknown subnet %s.\n", item);
328                 }
329
330                 return 1;
331         }
332
333         return 0;
334 }
335
336 int info(int fd, const char *item) {
337         if(check_id(item)) {
338                 return info_node(fd, item);
339         }
340
341         if(strchr(item, '.') || strchr(item, ':')) {
342                 return info_subnet(fd, item);
343         }
344
345         fprintf(stderr, "Argument is not a node name, subnet or address.\n");
346         return 1;
347 }