minor
[tinc] / src / sptps_test.c
1 /*
2     sptps_test.c -- Simple Peer-to-Peer Security test program
3     Copyright (C) 2011-2014 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 #ifdef HAVE_LINUX
23 #include <linux/if_tun.h>
24 #endif
25
26 #include <getopt.h>
27
28 #include "crypto.h"
29 #include "ecdsa.h"
30 #include "sptps.h"
31 #include "utils.h"
32
33 // Symbols necessary to link with logger.o
34 bool send_request(void *c, const char *msg, ...) {
35         return false;
36 }
37 struct list_t *connection_list = NULL;
38 bool send_meta(void *c, const char *msg, int len) {
39         return false;
40 }
41 char *logfilename = NULL;
42 bool do_detach = false;
43 struct timeval now;
44
45 static bool special;
46 static bool verbose;
47 static bool readonly;
48 static bool writeonly;
49 static int in = 0;
50 static int out = 1;
51 static int addressfamily = AF_UNSPEC;
52
53 static bool send_data(void *handle, uint8_t type, const void *data, size_t len) {
54         char hex[len * 2 + 1];
55         bin2hex(data, hex, len);
56
57         if(verbose) {
58                 fprintf(stderr, "Sending %d bytes of data:\n%s\n", (int)len, hex);
59         }
60
61         const int *sock = handle;
62
63         if(send(*sock, data, len, 0) != len) {
64                 return false;
65         }
66
67         return true;
68 }
69
70 static bool receive_record(void *handle, uint8_t type, const void *data, uint16_t len) {
71         if(verbose) {
72                 fprintf(stderr, "Received type %d record of %u bytes:\n", type, len);
73         }
74
75         if(!writeonly) {
76                 write(out, data, len);
77         }
78
79         return true;
80 }
81
82 static struct option const long_options[] = {
83         {"datagram", no_argument, NULL, 'd'},
84         {"quit", no_argument, NULL, 'q'},
85         {"readonly", no_argument, NULL, 'r'},
86         {"writeonly", no_argument, NULL, 'w'},
87         {"packet-loss", required_argument, NULL, 'L'},
88         {"replay-window", required_argument, NULL, 'W'},
89         {"special", no_argument, NULL, 's'},
90         {"verbose", required_argument, NULL, 'v'},
91         {"help", no_argument, NULL, 1},
92         {NULL, 0, NULL, 0}
93 };
94
95 const char *program_name;
96
97 static void usage() {
98         fprintf(stderr, "Usage: %s [options] my_ed25519_key_file his_ed25519_key_file [host] port\n\n", program_name);
99         fprintf(stderr, "Valid options are:\n"
100                 "  -d, --datagram          Enable datagram mode.\n"
101                 "  -q, --quit              Quit when EOF occurs on stdin.\n"
102                 "  -r, --readonly          Only send data from the socket to stdout.\n"
103 #ifdef HAVE_LINUX
104                 "  -t, --tun               Use a tun device instead of stdio.\n"
105 #endif
106                 "  -w, --writeonly         Only send data from stdin to the socket.\n"
107                 "  -L, --packet-loss RATE  Fake packet loss of RATE percent.\n"
108                 "  -R, --replay-window N   Set replay window to N bytes.\n"
109                 "  -s, --special           Enable special handling of lines starting with #, ^ and $.\n"
110                 "  -v, --verbose           Display debug messages.\n"
111                 "  -4                      Use IPv4.\n"
112                 "  -6                      Use IPv6.\n"
113                 "\n");
114         fprintf(stderr, "Report bugs to tinc@tinc-vpn.org.\n");
115 }
116
117 int main(int argc, char *argv[]) {
118         program_name = argv[0];
119         bool initiator = false;
120         bool datagram = false;
121 #ifdef HAVE_LINUX
122         bool tun = false;
123 #endif
124         int packetloss = 0;
125         int r;
126         int option_index = 0;
127         ecdsa_t *mykey = NULL, *hiskey = NULL;
128         bool quit = false;
129
130         while((r = getopt_long(argc, argv, "dqrstwL:W:v46", long_options, &option_index)) != EOF) {
131                 switch(r) {
132                 case 0:   /* long option */
133                         break;
134
135                 case 'd': /* datagram mode */
136                         datagram = true;
137                         break;
138
139                 case 'q': /* close connection on EOF from stdin */
140                         quit = true;
141                         break;
142
143                 case 'r': /* read only */
144                         readonly = true;
145                         break;
146
147                 case 't': /* read only */
148 #ifdef HAVE_LINUX
149                         tun = true;
150 #else
151                         fprintf(stderr, "--tun is only supported on Linux.\n");
152                         usage();
153                         return 1;
154 #endif
155                         break;
156
157                 case 'w': /* write only */
158                         writeonly = true;
159                         break;
160
161                 case 'L': /* packet loss rate */
162                         packetloss = atoi(optarg);
163                         break;
164
165                 case 'W': /* replay window size */
166                         sptps_replaywin = atoi(optarg);
167                         break;
168
169                 case 'v': /* be verbose */
170                         verbose = true;
171                         break;
172
173                 case 's': /* special character handling */
174                         special = true;
175                         break;
176
177                 case '?': /* wrong options */
178                         usage();
179                         return 1;
180
181                 case '4': /* IPv4 */
182                         addressfamily = AF_INET;
183                         break;
184
185                 case '6': /* IPv6 */
186                         addressfamily = AF_INET6;
187                         break;
188
189                 case 1: /* help */
190                         usage();
191                         return 0;
192
193                 default:
194                         break;
195                 }
196         }
197
198         argc -= optind - 1;
199         argv += optind - 1;
200
201         if(argc < 4 || argc > 5) {
202                 fprintf(stderr, "Wrong number of arguments.\n");
203                 usage();
204                 return 1;
205         }
206
207         if(argc > 4) {
208                 initiator = true;
209         }
210
211         srand(time(NULL));
212
213 #ifdef HAVE_LINUX
214
215         if(tun) {
216                 in = out = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
217
218                 if(in < 0) {
219                         fprintf(stderr, "Could not open tun device: %s\n", strerror(errno));
220                         return 1;
221                 }
222
223                 struct ifreq ifr = {
224                         .ifr_flags = IFF_TUN
225                 };
226
227                 if(ioctl(in, TUNSETIFF, &ifr)) {
228                         fprintf(stderr, "Could not configure tun interface: %s\n", strerror(errno));
229                         return 1;
230                 }
231
232                 ifr.ifr_name[IFNAMSIZ - 1] = 0;
233                 fprintf(stderr, "Using tun interface %s\n", ifr.ifr_name);
234         }
235
236 #endif
237
238 #ifdef HAVE_MINGW
239         static struct WSAData wsa_state;
240
241         if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
242                 return 1;
243         }
244
245 #endif
246
247         struct addrinfo *ai, hint;
248         memset(&hint, 0, sizeof(hint));
249
250         hint.ai_family = addressfamily;
251         hint.ai_socktype = datagram ? SOCK_DGRAM : SOCK_STREAM;
252         hint.ai_protocol = datagram ? IPPROTO_UDP : IPPROTO_TCP;
253         hint.ai_flags = initiator ? 0 : AI_PASSIVE;
254
255         if(getaddrinfo(initiator ? argv[3] : NULL, initiator ? argv[4] : argv[3], &hint, &ai) || !ai) {
256                 fprintf(stderr, "getaddrinfo() failed: %s\n", sockstrerror(sockerrno));
257                 return 1;
258         }
259
260         int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
261
262         if(sock < 0) {
263                 fprintf(stderr, "Could not create socket: %s\n", sockstrerror(sockerrno));
264                 return 1;
265         }
266
267         int one = 1;
268         setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
269
270         if(initiator) {
271                 if(connect(sock, ai->ai_addr, ai->ai_addrlen)) {
272                         fprintf(stderr, "Could not connect to peer: %s\n", sockstrerror(sockerrno));
273                         return 1;
274                 }
275
276                 fprintf(stderr, "Connected\n");
277         } else {
278                 if(bind(sock, ai->ai_addr, ai->ai_addrlen)) {
279                         fprintf(stderr, "Could not bind socket: %s\n", sockstrerror(sockerrno));
280                         return 1;
281                 }
282
283                 if(!datagram) {
284                         if(listen(sock, 1)) {
285                                 fprintf(stderr, "Could not listen on socket: %s\n", sockstrerror(sockerrno));
286                                 return 1;
287                         }
288
289                         fprintf(stderr, "Listening...\n");
290
291                         sock = accept(sock, NULL, NULL);
292
293                         if(sock < 0) {
294                                 fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno));
295                                 return 1;
296                         }
297                 } else {
298                         fprintf(stderr, "Listening...\n");
299
300                         char buf[65536];
301                         struct sockaddr addr;
302                         socklen_t addrlen = sizeof(addr);
303
304                         if(recvfrom(sock, buf, sizeof(buf), MSG_PEEK, &addr, &addrlen) <= 0) {
305                                 fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno));
306                                 return 1;
307                         }
308
309                         if(connect(sock, &addr, addrlen)) {
310                                 fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno));
311                                 return 1;
312                         }
313                 }
314
315                 fprintf(stderr, "Connected\n");
316         }
317
318         crypto_init();
319
320         FILE *fp = fopen(argv[1], "r");
321
322         if(!fp) {
323                 fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno));
324                 return 1;
325         }
326
327         if(!(mykey = ecdsa_read_pem_private_key(fp))) {
328                 return 1;
329         }
330
331         fclose(fp);
332
333         fp = fopen(argv[2], "r");
334
335         if(!fp) {
336                 fprintf(stderr, "Could not open %s: %s\n", argv[2], strerror(errno));
337                 return 1;
338         }
339
340         if(!(hiskey = ecdsa_read_pem_public_key(fp))) {
341                 return 1;
342         }
343
344         fclose(fp);
345
346         if(verbose) {
347                 fprintf(stderr, "Keys loaded\n");
348         }
349
350         sptps_t s;
351
352         if(!sptps_start(&s, &sock, initiator, datagram, mykey, hiskey, "sptps_test", 10, send_data, receive_record)) {
353                 return 1;
354         }
355
356         while(true) {
357                 if(writeonly && readonly) {
358                         break;
359                 }
360
361                 char buf[65535] = "";
362
363                 fd_set fds;
364                 FD_ZERO(&fds);
365 #ifndef HAVE_MINGW
366
367                 if(!readonly && s.instate) {
368                         FD_SET(in, &fds);
369                 }
370
371 #endif
372                 FD_SET(sock, &fds);
373
374                 if(select(sock + 1, &fds, NULL, NULL, NULL) <= 0) {
375                         return 1;
376                 }
377
378                 if(FD_ISSET(in, &fds)) {
379                         ssize_t len = read(in, buf, sizeof(buf));
380
381                         if(len < 0) {
382                                 fprintf(stderr, "Could not read from stdin: %s\n", strerror(errno));
383                                 return 1;
384                         }
385
386                         if(len == 0) {
387                                 if(quit) {
388                                         break;
389                                 }
390
391                                 readonly = true;
392                                 continue;
393                         }
394
395                         if(special && buf[0] == '#') {
396                                 s.outseqno = atoi(buf + 1);
397                         }
398
399                         if(special && buf[0] == '^') {
400                                 sptps_send_record(&s, SPTPS_HANDSHAKE, NULL, 0);
401                         } else if(special && buf[0] == '$') {
402                                 sptps_force_kex(&s);
403
404                                 if(len > 1) {
405                                         sptps_send_record(&s, 0, buf, len);
406                                 }
407                         } else if(!sptps_send_record(&s, buf[0] == '!' ? 1 : 0, buf, (len == 1 && buf[0] == '\n') ? 0 : buf[0] == '*' ? sizeof(buf) : len)) {
408                                 return 1;
409                         }
410                 }
411
412                 if(FD_ISSET(sock, &fds)) {
413                         ssize_t len = recv(sock, buf, sizeof(buf), 0);
414
415                         if(len < 0) {
416                                 fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno));
417                                 return 1;
418                         }
419
420                         if(len == 0) {
421                                 fprintf(stderr, "Connection terminated by peer.\n");
422                                 break;
423                         }
424
425                         if(verbose) {
426                                 char hex[len * 2 + 1];
427                                 bin2hex(buf, hex, len);
428                                 fprintf(stderr, "Received %d bytes of data:\n%s\n", (int)len, hex);
429                         }
430
431                         if(packetloss && (rand() % 100) < packetloss) {
432                                 if(verbose) {
433                                         fprintf(stderr, "Dropped.\n");
434                                 }
435
436                                 continue;
437                         }
438
439                         char *bufp = buf;
440
441                         while(len) {
442                                 size_t done = sptps_receive_data(&s, bufp, len);
443
444                                 if(!done) {
445                                         if(!datagram) {
446                                                 return 1;
447                                         }
448                                 }
449
450                                 bufp += done;
451                                 len -= done;
452                         }
453                 }
454         }
455
456         if(!sptps_stop(&s)) {
457                 return 1;
458         }
459
460         return 0;
461 }