xref: /dragonfly/usr.bin/vknet/vknet.c (revision 9f3fc534)
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 
500