xref: /dragonfly/usr.bin/vknet/vknet.c (revision 00cac4e7)
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 [-C] [-b local-bridge] [-B remote-bridge] [-r delay[:retries]]
38  *       local-spec [user@]remote[:remote-spec]
39  * vknet -S [-b local-bridge] local-spec	(server mode)
40  *
41  * Connect a SOCK_SEQPACKET socket or TUN device on the local host with
42  * a SOCK_SEQPACKET socket or TUN device on the remote host through a SSH
43  * connection.  When a TUN device is specified it may be optionally bridged.
44  *
45  * This program expects packetized reads and writes on the local and remote
46  * sides and will re-block them over the SSH stream.
47  */
48 
49 #include "vknet.h"
50 
51 static void vknet_blastaway(ioinfo_t ios, ioinfo_t iod);
52 static void *vknet_stream(void *arg);
53 static void vknet_connect(ioinfo_t ios,
54 			  const char *localSide, const char *localBridge);
55 static pid_t vknet_execssh(int fdin, int fdout, int compressOpt,
56 			   const char *remoteSide, const char *remoteBridge);
57 static void usage(void);
58 
59 pthread_mutex_t MasterLock;
60 
61 int
62 main(int ac, char **av)
63 {
64 	int compressOpt = 0;
65 	int remoteOpt = 0;
66 	const char *localBridge = NULL;
67 	const char *remoteBridge = NULL;
68 	const char *localSide;
69 	const char *remoteSide;
70 	char *ptr;
71 	int c;
72 	int retriesOpt = -1;
73 	int timeoutOpt = -1;
74 	pid_t sshpid = -1;
75 	pid_t p;
76 	struct ioinfo ios;
77 	struct ioinfo iod;
78 
79 	while ((c = getopt(ac, av, "b:B:r:CS")) != -1) {
80 		switch (c) {
81 		case 'b':
82 			localBridge = optarg;
83 			break;
84 		case 'B':
85 			remoteBridge = optarg;
86 			break;
87 		case 'r':
88 			timeoutOpt = strtol(optarg, &ptr, 0);
89 			if (ptr && *ptr == ':')
90 				retriesOpt = strtol(ptr + 1, NULL, 0);
91 			break;
92 		case 'S':
93 			remoteOpt = 1;
94 			break;
95 		case 'C':
96 			compressOpt = 1;
97 			break;
98 		default:
99 			usage();
100 		}
101 	}
102 	av += optind;
103 	ac -= optind;
104 
105 	/*
106 	 * Local and remote arguments.
107 	 */
108 	if (remoteOpt) {
109 		if (ac != 1)
110 			usage();
111 		localSide = av[0];
112 		remoteSide = NULL;
113 	} else {
114 		if (ac != 2)
115 			usage();
116 		localSide = av[0];
117 		remoteSide = av[1];
118 	}
119 
120 	pthread_mutex_init(&MasterLock, NULL);
121 
122 retry:
123 	/*
124 	 * Setup connections
125 	 */
126 	vknet_connect(&ios, localSide, localBridge);
127 	if (remoteOpt) {
128 		iod.fdin = 0;
129 		iod.fdout = 1;
130 	} else {
131 		int fds[2];
132 
133 		if (pipe(fds) < 0) {
134 			perror("pipe");
135 			exit(1);
136 		}
137 		sshpid = vknet_execssh(fds[1], fds[1], compressOpt,
138 			      remoteSide, remoteBridge);
139 		close(fds[1]);
140 		iod.fdin = fds[0];
141 		iod.fdout = fds[0];
142 	}
143 
144 	/*
145 	 * Blast away, timeout/retry on failure
146 	 */
147 	vknet_blastaway(&ios, &iod);
148 
149 	/*
150 	 * Terminate child process
151 	 */
152 	if (sshpid > 0) {
153 		if (kill(sshpid, SIGTERM) != 0)
154 			perror("kill");
155 		while ((p = waitpid(sshpid, NULL, 0)) != sshpid) {
156 			if (p < 0 && errno != EINTR)
157 				break;
158 		}
159 		sshpid = -1;
160 	}
161 
162 	/*
163 	 * Handle timeout/retries
164 	 */
165 	if (timeoutOpt >= 0 && retriesOpt != 0) {
166 		printf("timeout %d retries %d\n", timeoutOpt, retriesOpt);
167 		if (timeoutOpt > 0)
168 			sleep(timeoutOpt);
169 		if (retriesOpt > 0)
170 			--retriesOpt;
171 		goto retry;
172 	}
173 	exit(0);
174 }
175 
176 static void
177 vknet_blastaway(ioinfo_t ios, ioinfo_t iod)
178 {
179 	struct streaminfo stream1;
180 	struct streaminfo stream2;
181 
182 	pthread_mutex_lock(&MasterLock);
183 	stream1.fdin = ios->fdin;
184 	stream1.fdout = iod->fdout;
185 	stream1.flags = REBLOCK_OUT;
186 	stream1.other = &stream2;
187 	stream2.fdin = iod->fdin;
188 	stream2.fdout = ios->fdout;
189 	stream2.flags = REBLOCK_IN;
190 	stream2.other = &stream1;
191 	pthread_create(&stream1.thread, NULL, vknet_stream, &stream1);
192 	pthread_create(&stream2.thread, NULL, vknet_stream, &stream2);
193 	pthread_mutex_unlock(&MasterLock);
194 	pthread_join(stream1.thread, NULL);
195 	pthread_join(stream2.thread, NULL);
196 }
197 
198 /*
199  * Transfer packets between two descriptors
200  */
201 static
202 void *
203 vknet_stream(void *arg)
204 {
205 	streaminfo_t stream = arg;
206 	struct blkhead head;
207 	u_int8_t *pkt;
208 	int bytes;
209 	int n;
210 	int r;
211 
212 	/*
213 	 * Synchronize with master thread, then loop
214 	 */
215 	pthread_mutex_lock(&MasterLock);
216 	pthread_mutex_unlock(&MasterLock);
217 
218 	pkt = malloc(MAXPKT);
219 
220 	for (;;) {
221 		/*
222 		 * Input side
223 		 */
224 		if (stream->flags & REBLOCK_IN) {
225 			bytes = sizeof(head);
226 			for (n = 0; n < bytes; n += r) {
227 				r = read(stream->fdin, (char *)&head + n,
228 					 bytes - n);
229 				if (r <= 0)
230 					break;
231 			}
232 			if (n != bytes)
233 				break;
234 			if (le32toh(head.magic) != MAGIC)
235 				break;
236 			bytes = le32toh(head.bytes);
237 			if (bytes <= 0 || bytes > MAXPKT)
238 				break;
239 			for (n = 0; n < bytes; n += r) {
240 				r = read(stream->fdin, pkt + n, bytes - n);
241 				if (r <= 0)
242 					break;
243 			}
244 			if (n != bytes)
245 				break;
246 		} else {
247 			bytes = read(stream->fdin, pkt, MAXPKT);
248 			if (bytes <= 0)
249 				break;
250 		}
251 
252 		/*
253 		 * Output side
254 		 */
255 		if (stream->flags & REBLOCK_OUT) {
256 			head.magic = htole32(MAGIC);
257 			head.bytes = htole32(bytes);
258 			if (write(stream->fdout, &head, sizeof(head)) != sizeof(head))
259 				break;
260 			if (write(stream->fdout, pkt, bytes) != bytes)
261 				break;
262 		} else {
263 			if (write(stream->fdout, pkt, bytes) != bytes)
264 				break;
265 		}
266 	}
267 	free(pkt);
268 	close(stream->fdin);
269 	close(stream->fdout);
270 	pthread_cancel(stream->other->thread);
271 	pthread_exit(NULL);
272 }
273 
274 /*
275  * vknet_connect() - Connect to local side, optionally find or bridge the tap
276  *		     interface.
277  */
278 static void
279 vknet_connect(ioinfo_t io, const char *localSide, const char *localBridge)
280 {
281 	struct ifreq ifr;
282 	struct ifaliasreq ifra;
283 	char *buf = NULL;
284 	int tap_fd;
285 	int tap_unit;
286 	int i;
287 	int s;
288 	int flags;
289 
290 	tap_unit = -1;
291 	tap_fd = -1;
292 
293 	if (strcmp(localSide, "auto") == 0) {
294 		for (i = 0; ; ++i) {
295 			asprintf(&buf, "/dev/tap%d", i);
296 			tap_fd = open(buf, O_RDWR | O_NONBLOCK);
297 			free(buf);
298 			if (tap_fd >= 0 || errno == ENOENT) {
299 				tap_unit = i;
300 				break;
301 			}
302 		}
303 	} else if (strncmp(localSide, "tap", 3) == 0) {
304 		asprintf(&buf, "/dev/%s", localSide);
305 		tap_fd = open(buf, O_RDWR | O_NONBLOCK);
306 		tap_unit = strtol(localSide + 3, NULL, 10);
307 		free(buf);
308 	} else if ((tap_fd = open(localSide, O_RDWR | O_NONBLOCK)) >= 0) {
309 		const char *ptr = localSide + strlen(localSide);
310 		while (ptr > localSide && ptr[-1] >= '0' && ptr[-1] <= '9')
311 			--ptr;
312 		tap_unit = strtol(ptr, NULL, 10);
313 	} else {
314 		struct sockaddr_un sunx;
315 		int len;
316 
317 		snprintf(sunx.sun_path, sizeof(sunx.sun_path), "%s", localSide);
318 		len = offsetof(struct sockaddr_un,
319 			       sun_path[strlen(sunx.sun_path)]);
320 		++len;	/* include nul */
321 		sunx.sun_family = AF_UNIX;
322 		sunx.sun_len = len;
323 
324 		tap_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
325 		if (tap_fd >= 0)	 {
326 			if (connect(tap_fd, (void *)&sunx, len) < 0) {
327 				close(tap_fd);
328 				tap_fd = -1;
329 			}
330 		}
331 	}
332 
333 	if (tap_fd < 0) {
334 		err(1, "Unable to connect to %s", localSide);
335 		/* NOT REACHED */
336 	}
337 
338 	fcntl(tap_fd, F_SETFL, 0);
339 	io->fdin = tap_fd;
340 	io->fdout = tap_fd;
341 
342 	/*
343 	 * If this isn't a TAP device we are done.
344 	 */
345 	if (tap_unit < 0)
346 		return;
347 
348 	/*
349 	 * Bring up the TAP interface
350 	 */
351 	bzero(&ifr, sizeof(ifr));
352 	bzero(&ifra, sizeof(ifra));
353 	snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tap%d", tap_unit);
354 	snprintf(ifra.ifra_name, sizeof(ifra.ifra_name), "tap%d", tap_unit);
355 
356 	s = socket(AF_INET, SOCK_DGRAM, 0);
357 
358 #if 0
359 	/*
360 	 * Set the interface address if in Secure mode.
361 	 */
362 	if (SecureOpt) {
363 		struct sockaddr_in *in;
364 
365 		in = (void *)&ifra.ifra_addr;
366 		in->sin_family = AF_INET;
367 		in->sin_len = sizeof(ifra.ifra_addr);
368 		in->sin_addr = NetAddress;
369 		in = (void *)&ifra.ifra_mask;
370 		in->sin_family = AF_INET;
371 		in->sin_len = sizeof(ifra.ifra_mask);
372 		in->sin_addr = NetMask;
373 		if (ioctl(s, SIOCAIFADDR, &ifra) < 0) {
374 			perror("Unable to set address on tap interface");
375 			exit(1);
376 		}
377 	}
378 #endif
379 
380 	/*
381 	 * Turn up the interface
382 	 */
383 	flags = IFF_UP;
384 	if (ioctl(s, SIOCGIFFLAGS, &ifr) >= 0) {
385 		bzero(&ifr, sizeof(ifr));
386 		snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tap%d", tap_unit);
387 		ifr.ifr_flags |= flags & 0xFFFF;
388 		ifr.ifr_flagshigh |= flags >> 16;
389 		if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) {
390 			perror("Unable to set IFF_UP on tap interface");
391 			exit(1);
392 		}
393 	}
394 
395 	/*
396 	 * If a bridge was specified associate the tap interface with the
397 	 * bridge.
398 	 */
399 	if (localBridge) {
400 		struct ifbreq ifbr;
401 		struct ifdrv ifd;
402 
403 		/*
404 		 * Create the bridge if necessary.
405 		 */
406 		bzero(&ifr, sizeof(ifr));
407 		snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", localBridge);
408 		if (ioctl(s, SIOCIFCREATE, &ifr) < 0) {
409 			if (errno != EEXIST) {
410 				perror("Unable to create bridge interface");
411 				exit(1);
412 			}
413 		}
414 
415 
416 		/*
417 		 * Add the tap interface to the bridge
418 		 */
419 		bzero(&ifbr, sizeof(ifbr));
420 		snprintf(ifbr.ifbr_ifsname, sizeof(ifbr.ifbr_ifsname),
421 			 "tap%d", tap_unit);
422 
423 		bzero(&ifd, sizeof(ifd));
424 		snprintf(ifd.ifd_name, sizeof(ifd.ifd_name), "%s", localBridge);
425 		ifd.ifd_cmd = BRDGADD;
426 		ifd.ifd_len = sizeof(ifbr);
427 		ifd.ifd_data = &ifbr;
428 
429 		if (ioctl(s, SIOCSDRVSPEC, &ifd) < 0) {
430 			if (errno != EEXIST) {
431 				perror("Unable to add tap ifc to bridge!");
432 				exit(1);
433 			}
434 		}
435 	}
436 	close(s);
437 }
438 
439 /*
440  * Connect to the remote machine with ssh and set up a stream
441  */
442 static pid_t
443 vknet_execssh(int fdin, int fdout, int compressOpt,
444 	      const char *remoteSide, const char *remoteBridge)
445 {
446 	char *remoteHost;
447 	char *remotePath;
448 	const char *av[24];
449 	int ac;
450 	pid_t pid;
451 
452 	/*
453 	 * Fork / parent returns.
454 	 */
455 	if ((pid = fork()) > 0)
456 		return pid;
457 	if (pid < 0) {
458 		perror("fork");
459 		exit(1);
460 	}
461 
462 	/*
463 	 * Setup stdin, stdout
464 	 */
465 	assert(fdin > 2);
466 	assert(fdout > 2);
467 	dup2(fdin, 0);
468 	dup2(fdout, 1);
469 	close(fdin);
470 	close(fdout);
471 
472 	/*
473 	 * Set up arguments
474 	 */
475 	remoteHost = strdup(remoteSide);
476 	if ((remotePath = strchr(remoteHost, ':')) != NULL) {
477 		*remotePath++ = 0;
478 	} else {
479 		remotePath = strdup("/var/run/vknet");
480 	}
481 	ac = 0;
482 	av[ac++] = "ssh";
483 	if (compressOpt)
484 		av[ac++] = "-C";
485 	av[ac++] = "-x";
486 	av[ac++] = "-T";
487 	av[ac++] = "-e";
488 	av[ac++] = "none";
489 	av[ac++] = remoteHost;
490 	av[ac++] = "exec";
491 	av[ac++] = "vknet";
492 	av[ac++] = "-S";
493 	if (remoteBridge) {
494 		av[ac++] = "-b";
495 		av[ac++] = remoteBridge;
496 	}
497 	av[ac++] = remotePath;
498 	av[ac++] = NULL;
499 	execv("/usr/bin/ssh", (void *)av);
500 	exit(1);
501 }
502 
503 /*
504  * Misc
505  */
506 static
507 void
508 usage(void)
509 {
510 	fprintf(stderr,
511 		"vknet [-C] [-b local-bridge] [-B remote-bridge] [-r delay[:retries]]\n"
512 		"      local-spec [user@]remote[:remote-spec]\n"
513 		"vknet -S [-b local-bridge] local-spec\n"
514 	);
515 	exit(1);
516 }
517 
518