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