Merge revision 1.21 and 1.22 from OpenBSD. Add a missing "the" to the
[dragonfly.git] / usr.bin / vknet / vknet.c
1 /*
2  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
3  * 
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  * 
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  * 
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  * 
34  * $DragonFly: src/usr.bin/vknet/vknet.c,v 1.1 2008/05/27 23:26:38 dillon Exp $
35  */
36 /*
37  * vknet [-r timeout[:retries]] [-C] [-b local_bridge] [-B remote_bridge] local remote
38  * vknet -S [-b local_bridge] local     (server mode)
39  *
40  * Connect a SOCK_SEQPACKET socket or TUN device on the local host with
41  * a SOCK_SEQPACKET socket or TUN device on the remote host through a SSH
42  * connection.  When a TUN device is specified it may be optionally bridged.
43  *
44  * This program expects packetized reads and writes on the local and remote
45  * sides and will re-block them over the SSH stream.
46  */
47
48 #include "vknet.h"
49
50 static void vknet_blastaway(ioinfo_t ios, ioinfo_t iod);
51 static void *vknet_stream(void *arg);
52 static void vknet_connect(ioinfo_t ios,
53                           const char *localSide, const char *localBridge);
54 static void vknet_execssh(int fdin, int fdout, int compressOpt, 
55                           const char *remoteSide, const char *remoteBridge);
56 static void usage(void);
57
58 pthread_mutex_t MasterLock;
59
60 int
61 main(int ac, char **av)
62 {
63         int compressOpt = 0;
64         int remoteOpt = 0;
65         const char *localBridge = NULL;
66         const char *remoteBridge = NULL;
67         const char *localSide;
68         const char *remoteSide;
69         char *ptr;
70         int c;
71         int retriesOpt = -1;
72         int timeoutOpt = -1;
73         struct ioinfo ios;
74         struct ioinfo iod;
75
76         while ((c = getopt(ac, av, "b:B:r:CS")) != -1) {
77                 switch (c) {
78                 case 'b':
79                         localBridge = optarg;
80                         break;
81                 case 'B':
82                         remoteBridge = optarg;
83                         break;
84                 case 'r':
85                         timeoutOpt = strtol(optarg, &ptr, 0);
86                         if (ptr && *ptr == ':')
87                                 retriesOpt = strtol(ptr + 1, NULL, 0);
88                         break;
89                 case 'S':
90                         remoteOpt = 1;
91                         break;
92                 case 'C':
93                         compressOpt = 1;
94                         break;
95                 default:
96                         usage();
97                 }
98         }
99         av += optind;
100         ac -= optind;
101
102         /*
103          * Local and remote arguments.
104          */
105         if (remoteOpt) {
106                 if (ac != 1)
107                         usage();
108                 localSide = av[0];
109                 remoteSide = NULL;
110         } else {
111                 if (ac != 2)
112                         usage();
113                 localSide = av[0];
114                 remoteSide = av[1];
115         }
116
117         pthread_mutex_init(&MasterLock, NULL);
118
119 retry:
120         /*
121          * Setup connections
122          */
123         vknet_connect(&ios, localSide, localBridge);
124         if (remoteOpt) {
125                 iod.fdin = 0;
126                 iod.fdout = 1;
127         } else {
128                 int fds[2];
129
130                 if (pipe(fds) < 0) {
131                         perror("pipe");
132                         exit(1);
133                 }
134                 vknet_execssh(fds[1], fds[1], compressOpt,
135                               remoteSide, remoteBridge);
136                 close(fds[1]);
137                 iod.fdin = fds[0];
138                 iod.fdout = fds[0];
139         }
140
141         /*
142          * Blast away, timeout/retry on failure
143          */
144         vknet_blastaway(&ios, &iod);
145
146         /*
147          * Handle timeout/retries
148          */
149         if (timeoutOpt >= 0 && retriesOpt != 0) {
150                 printf("timeout %d retries %d\n", timeoutOpt, retriesOpt);
151                 if (timeoutOpt > 0)
152                         sleep(timeoutOpt);
153                 if (retriesOpt > 0)
154                         --retriesOpt;
155                 goto retry;
156         }
157         exit(0);
158 }
159
160 static void
161 vknet_blastaway(ioinfo_t ios, ioinfo_t iod)
162 {
163         struct streaminfo stream1;
164         struct streaminfo stream2;
165
166         pthread_mutex_lock(&MasterLock);
167         stream1.fdin = ios->fdin;
168         stream1.fdout = iod->fdout;
169         stream1.flags = REBLOCK_OUT;
170         stream1.other = &stream2;
171         stream2.fdin = iod->fdin;
172         stream2.fdout = ios->fdout;
173         stream2.flags = REBLOCK_IN;
174         stream2.other = &stream1;
175         pthread_create(&stream1.thread, NULL, vknet_stream, &stream1);
176         pthread_create(&stream2.thread, NULL, vknet_stream, &stream2);
177         pthread_mutex_unlock(&MasterLock);
178         pthread_join(stream1.thread, NULL);
179         pthread_join(stream2.thread, NULL);
180 }
181
182 /*
183  * Transfer packets between two descriptors
184  */
185 static
186 void *
187 vknet_stream(void *arg)
188 {
189         streaminfo_t stream = arg;
190         struct blkhead head;
191         u_int8_t *pkt;
192         int bytes;
193         int n;
194         int r;
195
196         /*
197          * Synchronize with master thread, then loop
198          */
199         pthread_mutex_lock(&MasterLock);
200         pthread_mutex_unlock(&MasterLock);
201
202         pkt = malloc(MAXPKT);
203
204         for (;;) {
205                 /*
206                  * Input side
207                  */
208                 if (stream->flags & REBLOCK_IN) {
209                         bytes = sizeof(head);
210                         for (n = 0; n < bytes; n += r) {
211                                 r = read(stream->fdin, (char *)&head + n,
212                                          bytes - n);
213                                 if (r <= 0)
214                                         break;
215                         }
216                         if (n != bytes)
217                                 break;
218                         if (le32toh(head.magic) != MAGIC)
219                                 break;
220                         bytes = le32toh(head.bytes);
221                         if (bytes <= 0 || bytes > MAXPKT)
222                                 break;
223                         for (n = 0; n < bytes; n += r) {
224                                 r = read(stream->fdin, pkt + n, bytes - n);
225                                 if (r <= 0)
226                                         break;
227                         }
228                         if (n != bytes)
229                                 break;
230                 } else {
231                         bytes = read(stream->fdin, pkt, MAXPKT);
232                         if (bytes <= 0)
233                                 break;
234                 }
235
236                 /*
237                  * Output side
238                  */
239                 if (stream->flags & REBLOCK_OUT) {
240                         head.magic = htole32(MAGIC);
241                         head.bytes = htole32(bytes);
242                         if (write(stream->fdout, &head, sizeof(head)) != sizeof(head))
243                                 break;
244                         if (write(stream->fdout, pkt, bytes) != bytes)
245                                 break;
246                 } else {
247                         if (write(stream->fdout, pkt, bytes) != bytes)
248                                 break;
249                 }
250         }
251         free(pkt);
252         close(stream->fdin);
253         close(stream->fdout);
254         pthread_cancel(stream->other->thread);
255         pthread_exit(NULL);
256 }
257
258 /*
259  * vknet_connect() - Connect to local side, optinally find or bridge the tap
260  *                   interface.
261  */
262 static void
263 vknet_connect(ioinfo_t io, const char *localSide, const char *localBridge)
264 {
265         struct ifreq ifr;
266         struct ifaliasreq ifra;
267         char *buf = NULL;
268         int tap_fd;
269         int tap_unit;
270         int i;
271         int s;
272         int flags;
273
274         tap_unit = -1;
275         tap_fd = -1;
276
277         if (strcmp(localSide, "auto") == 0) {
278                 for (i = 0; ; ++i) {
279                         asprintf(&buf, "/dev/tap%d", i);
280                         tap_fd = open(buf, O_RDWR | O_NONBLOCK);
281                         free(buf);
282                         if (tap_fd >= 0 || errno == ENOENT) {
283                                 tap_unit = i;
284                                 break;
285                         }
286                 }
287         } else if (strncmp(localSide, "tap", 3) == 0) {
288                 asprintf(&buf, "/dev/%s", localSide);
289                 tap_fd = open(buf, O_RDWR | O_NONBLOCK);
290                 tap_unit = strtol(localSide + 3, NULL, 10);
291                 free(buf);
292         } else if ((tap_fd = open(localSide, O_RDWR | O_NONBLOCK)) >= 0) {
293                 const char *ptr = localSide + strlen(localSide);
294                 while (ptr > localSide && ptr[-1] >= '0' && ptr[-1] <= '9')
295                         --ptr;
296                 tap_unit = strtol(ptr, NULL, 10);
297         } else {
298                 struct sockaddr_un sunx;
299                 int len;
300
301                 snprintf(sunx.sun_path, sizeof(sunx.sun_path), "%s", localSide);
302                 len = offsetof(struct sockaddr_un,
303                                sun_path[strlen(sunx.sun_path)]);
304                 ++len;  /* include nul */
305                 sunx.sun_family = AF_UNIX;
306                 sunx.sun_len = len;
307
308                 tap_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
309                 if (tap_fd >= 0)         {
310                         if (connect(tap_fd, (void *)&sunx, len) < 0) {
311                                 close(tap_fd);
312                                 tap_fd = -1;
313                         }
314                 }
315         }
316
317         if (tap_fd < 0) {
318                 err(1, "Unable to connect to %s", localSide);
319                 /* NOT REACHED */
320         }
321
322         fcntl(tap_fd, F_SETFL, 0);
323         io->fdin = tap_fd;
324         io->fdout = tap_fd;
325
326         /*
327          * If this isn't a TAP device we are done.
328          */
329         if (tap_unit < 0)
330                 return;
331
332         /*
333          * Bring up the TAP interface
334          */
335         bzero(&ifr, sizeof(ifr));
336         bzero(&ifra, sizeof(ifra));
337         snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tap%d", tap_unit);
338         snprintf(ifra.ifra_name, sizeof(ifra.ifra_name), "tap%d", tap_unit);
339
340         s = socket(AF_INET, SOCK_DGRAM, 0);
341
342 #if 0
343         /*
344          * Set the interface address if in Secure mode.
345          */
346         if (SecureOpt) {
347                 struct sockaddr_in *in;
348
349                 in = (void *)&ifra.ifra_addr;
350                 in->sin_family = AF_INET;
351                 in->sin_len = sizeof(ifra.ifra_addr);
352                 in->sin_addr = NetAddress;
353                 in = (void *)&ifra.ifra_mask;
354                 in->sin_family = AF_INET;
355                 in->sin_len = sizeof(ifra.ifra_mask);
356                 in->sin_addr = NetMask;
357                 if (ioctl(s, SIOCAIFADDR, &ifra) < 0) {
358                         perror("Unable to set address on tap interface");
359                         exit(1);
360                 }
361         }
362 #endif
363
364         /*
365          * Turn up the interface
366          */
367         flags = IFF_UP;
368         if (ioctl(s, SIOCGIFFLAGS, &ifr) >= 0) {
369                 bzero(&ifr, sizeof(ifr));
370                 snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tap%d", tap_unit);
371                 ifr.ifr_flags |= flags & 0xFFFF;
372                 ifr.ifr_flagshigh |= flags >> 16;
373                 if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) {
374                         perror("Unable to set IFF_UP on tap interface");
375                         exit(1);
376                 }
377         }
378
379         /*
380          * If a bridge was specified associate the tap interface with the
381          * bridge.
382          */
383         if (localBridge) {
384                 struct ifbreq ifbr;
385                 struct ifdrv ifd;
386
387                 /*
388                  * Create the bridge if necessary.
389                  */
390                 bzero(&ifr, sizeof(ifr));
391                 snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", localBridge);
392                 if (ioctl(s, SIOCIFCREATE, &ifr) < 0) {
393                         if (errno != EEXIST) {
394                                 perror("Unable to create bridge interface");
395                                 exit(1);
396                         }
397                 }
398
399
400                 /*
401                  * Add the tap interface to the bridge
402                  */
403                 bzero(&ifbr, sizeof(ifbr));
404                 snprintf(ifbr.ifbr_ifsname, sizeof(ifbr.ifbr_ifsname),
405                          "tap%d", tap_unit);
406
407                 bzero(&ifd, sizeof(ifd));
408                 snprintf(ifd.ifd_name, sizeof(ifd.ifd_name), "%s", localBridge);
409                 ifd.ifd_cmd = BRDGADD;
410                 ifd.ifd_len = sizeof(ifbr);
411                 ifd.ifd_data = &ifbr;
412
413                 if (ioctl(s, SIOCSDRVSPEC, &ifd) < 0) {
414                         if (errno != EEXIST) {
415                                 perror("Unable to add tap ifc to bridge!");
416                                 exit(1);
417                         }
418                 }
419         }
420         close(s);
421 }
422
423 /*
424  * Connect to the remote machine with ssh and set up a stream
425  */
426 static void
427 vknet_execssh(int fdin, int fdout, int compressOpt, 
428               const char *remoteSide, const char *remoteBridge)
429 {
430         char *remoteHost;
431         char *remotePath;
432         const char *av[24];
433         int ac;
434         pid_t pid;
435
436         /*
437          * Fork / parent returns.
438          */
439         if ((pid = fork()) > 0)
440                 return;
441         if (pid < 0) {
442                 perror("fork");
443                 exit(1);
444         }
445
446         /*
447          * Setup stdin, stdout
448          */
449         assert(fdin > 2);
450         assert(fdout > 2);
451         dup2(fdin, 0);
452         dup2(fdout, 1);
453         close(fdin);
454         close(fdout);
455
456         /*
457          * Set up arguments
458          */
459         remoteHost = strdup(remoteSide);
460         if ((remotePath = strchr(remoteHost, ':')) != NULL) {
461                 *remotePath++ = 0;
462         } else {
463                 remotePath = strdup("/dev/vknet");
464         }
465         ac = 0;
466         av[ac++] = "ssh";
467         if (compressOpt)
468                 av[ac++] = "-C";
469         av[ac++] = "-x";
470         av[ac++] = "-T";
471         av[ac++] = "-e";
472         av[ac++] = "none";
473         av[ac++] = remoteHost;
474         av[ac++] = "exec";
475         av[ac++] = "vknet";
476         av[ac++] = "-S";
477         if (remoteBridge) {
478                 av[ac++] = "-b";
479                 av[ac++] = remoteBridge;
480         }
481         av[ac++] = remotePath;
482         av[ac++] = NULL;
483         execv("/usr/bin/ssh", (void *)av);
484 }
485
486 /*
487  * Misc
488  */
489 static
490 void
491 usage(void)
492 {
493         fprintf(stderr, 
494                 "vknet [-C] [-b local_bridge] [-B remote_bridge] local remote\n"
495                 "vknet -S [-b local_bridge] local\n"
496         );
497         exit(1);
498 }
499