xref: /dragonfly/usr.bin/vknet/vknet.c (revision 650094e1)
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 void 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 	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 		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 	 * Handle timeout/retries
149 	 */
150 	if (timeoutOpt >= 0 && retriesOpt != 0) {
151 		printf("timeout %d retries %d\n", timeoutOpt, retriesOpt);
152 		if (timeoutOpt > 0)
153 			sleep(timeoutOpt);
154 		if (retriesOpt > 0)
155 			--retriesOpt;
156 		goto retry;
157 	}
158 	exit(0);
159 }
160 
161 static void
162 vknet_blastaway(ioinfo_t ios, ioinfo_t iod)
163 {
164 	struct streaminfo stream1;
165 	struct streaminfo stream2;
166 
167 	pthread_mutex_lock(&MasterLock);
168 	stream1.fdin = ios->fdin;
169 	stream1.fdout = iod->fdout;
170 	stream1.flags = REBLOCK_OUT;
171 	stream1.other = &stream2;
172 	stream2.fdin = iod->fdin;
173 	stream2.fdout = ios->fdout;
174 	stream2.flags = REBLOCK_IN;
175 	stream2.other = &stream1;
176 	pthread_create(&stream1.thread, NULL, vknet_stream, &stream1);
177 	pthread_create(&stream2.thread, NULL, vknet_stream, &stream2);
178 	pthread_mutex_unlock(&MasterLock);
179 	pthread_join(stream1.thread, NULL);
180 	pthread_join(stream2.thread, NULL);
181 }
182 
183 /*
184  * Transfer packets between two descriptors
185  */
186 static
187 void *
188 vknet_stream(void *arg)
189 {
190 	streaminfo_t stream = arg;
191 	struct blkhead head;
192 	u_int8_t *pkt;
193 	int bytes;
194 	int n;
195 	int r;
196 
197 	/*
198 	 * Synchronize with master thread, then loop
199 	 */
200 	pthread_mutex_lock(&MasterLock);
201 	pthread_mutex_unlock(&MasterLock);
202 
203 	pkt = malloc(MAXPKT);
204 
205 	for (;;) {
206 		/*
207 		 * Input side
208 		 */
209 		if (stream->flags & REBLOCK_IN) {
210 			bytes = sizeof(head);
211 			for (n = 0; n < bytes; n += r) {
212 				r = read(stream->fdin, (char *)&head + n,
213 					 bytes - n);
214 				if (r <= 0)
215 					break;
216 			}
217 			if (n != bytes)
218 				break;
219 			if (le32toh(head.magic) != MAGIC)
220 				break;
221 			bytes = le32toh(head.bytes);
222 			if (bytes <= 0 || bytes > MAXPKT)
223 				break;
224 			for (n = 0; n < bytes; n += r) {
225 				r = read(stream->fdin, pkt + n, bytes - n);
226 				if (r <= 0)
227 					break;
228 			}
229 			if (n != bytes)
230 				break;
231 		} else {
232 			bytes = read(stream->fdin, pkt, MAXPKT);
233 			if (bytes <= 0)
234 				break;
235 		}
236 
237 		/*
238 		 * Output side
239 		 */
240 		if (stream->flags & REBLOCK_OUT) {
241 			head.magic = htole32(MAGIC);
242 			head.bytes = htole32(bytes);
243 			if (write(stream->fdout, &head, sizeof(head)) != sizeof(head))
244 				break;
245 			if (write(stream->fdout, pkt, bytes) != bytes)
246 				break;
247 		} else {
248 			if (write(stream->fdout, pkt, bytes) != bytes)
249 				break;
250 		}
251 	}
252 	free(pkt);
253 	close(stream->fdin);
254 	close(stream->fdout);
255 	pthread_cancel(stream->other->thread);
256 	pthread_exit(NULL);
257 }
258 
259 /*
260  * vknet_connect() - Connect to local side, optionally find or bridge the tap
261  *		     interface.
262  */
263 static void
264 vknet_connect(ioinfo_t io, const char *localSide, const char *localBridge)
265 {
266 	struct ifreq ifr;
267 	struct ifaliasreq ifra;
268 	char *buf = NULL;
269 	int tap_fd;
270 	int tap_unit;
271 	int i;
272 	int s;
273 	int flags;
274 
275 	tap_unit = -1;
276 	tap_fd = -1;
277 
278 	if (strcmp(localSide, "auto") == 0) {
279 		for (i = 0; ; ++i) {
280 			asprintf(&buf, "/dev/tap%d", i);
281 			tap_fd = open(buf, O_RDWR | O_NONBLOCK);
282 			free(buf);
283 			if (tap_fd >= 0 || errno == ENOENT) {
284 				tap_unit = i;
285 				break;
286 			}
287 		}
288 	} else if (strncmp(localSide, "tap", 3) == 0) {
289 		asprintf(&buf, "/dev/%s", localSide);
290 		tap_fd = open(buf, O_RDWR | O_NONBLOCK);
291 		tap_unit = strtol(localSide + 3, NULL, 10);
292 		free(buf);
293 	} else if ((tap_fd = open(localSide, O_RDWR | O_NONBLOCK)) >= 0) {
294 		const char *ptr = localSide + strlen(localSide);
295 		while (ptr > localSide && ptr[-1] >= '0' && ptr[-1] <= '9')
296 			--ptr;
297 		tap_unit = strtol(ptr, NULL, 10);
298 	} else {
299 		struct sockaddr_un sunx;
300 		int len;
301 
302 		snprintf(sunx.sun_path, sizeof(sunx.sun_path), "%s", localSide);
303 		len = offsetof(struct sockaddr_un,
304 			       sun_path[strlen(sunx.sun_path)]);
305 		++len;	/* include nul */
306 		sunx.sun_family = AF_UNIX;
307 		sunx.sun_len = len;
308 
309 		tap_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
310 		if (tap_fd >= 0)	 {
311 			if (connect(tap_fd, (void *)&sunx, len) < 0) {
312 				close(tap_fd);
313 				tap_fd = -1;
314 			}
315 		}
316 	}
317 
318 	if (tap_fd < 0) {
319 		err(1, "Unable to connect to %s", localSide);
320 		/* NOT REACHED */
321 	}
322 
323 	fcntl(tap_fd, F_SETFL, 0);
324 	io->fdin = tap_fd;
325 	io->fdout = tap_fd;
326 
327 	/*
328 	 * If this isn't a TAP device we are done.
329 	 */
330 	if (tap_unit < 0)
331 		return;
332 
333 	/*
334 	 * Bring up the TAP interface
335 	 */
336 	bzero(&ifr, sizeof(ifr));
337 	bzero(&ifra, sizeof(ifra));
338 	snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tap%d", tap_unit);
339 	snprintf(ifra.ifra_name, sizeof(ifra.ifra_name), "tap%d", tap_unit);
340 
341 	s = socket(AF_INET, SOCK_DGRAM, 0);
342 
343 #if 0
344 	/*
345 	 * Set the interface address if in Secure mode.
346 	 */
347 	if (SecureOpt) {
348 		struct sockaddr_in *in;
349 
350 		in = (void *)&ifra.ifra_addr;
351 		in->sin_family = AF_INET;
352 		in->sin_len = sizeof(ifra.ifra_addr);
353 		in->sin_addr = NetAddress;
354 		in = (void *)&ifra.ifra_mask;
355 		in->sin_family = AF_INET;
356 		in->sin_len = sizeof(ifra.ifra_mask);
357 		in->sin_addr = NetMask;
358 		if (ioctl(s, SIOCAIFADDR, &ifra) < 0) {
359 			perror("Unable to set address on tap interface");
360 			exit(1);
361 		}
362 	}
363 #endif
364 
365 	/*
366 	 * Turn up the interface
367 	 */
368 	flags = IFF_UP;
369 	if (ioctl(s, SIOCGIFFLAGS, &ifr) >= 0) {
370 		bzero(&ifr, sizeof(ifr));
371 		snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tap%d", tap_unit);
372 		ifr.ifr_flags |= flags & 0xFFFF;
373 		ifr.ifr_flagshigh |= flags >> 16;
374 		if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) {
375 			perror("Unable to set IFF_UP on tap interface");
376 			exit(1);
377 		}
378 	}
379 
380 	/*
381 	 * If a bridge was specified associate the tap interface with the
382 	 * bridge.
383 	 */
384 	if (localBridge) {
385 		struct ifbreq ifbr;
386 		struct ifdrv ifd;
387 
388 		/*
389 		 * Create the bridge if necessary.
390 		 */
391 		bzero(&ifr, sizeof(ifr));
392 		snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", localBridge);
393 		if (ioctl(s, SIOCIFCREATE, &ifr) < 0) {
394 			if (errno != EEXIST) {
395 				perror("Unable to create bridge interface");
396 				exit(1);
397 			}
398 		}
399 
400 
401 		/*
402 		 * Add the tap interface to the bridge
403 		 */
404 		bzero(&ifbr, sizeof(ifbr));
405 		snprintf(ifbr.ifbr_ifsname, sizeof(ifbr.ifbr_ifsname),
406 			 "tap%d", tap_unit);
407 
408 		bzero(&ifd, sizeof(ifd));
409 		snprintf(ifd.ifd_name, sizeof(ifd.ifd_name), "%s", localBridge);
410 		ifd.ifd_cmd = BRDGADD;
411 		ifd.ifd_len = sizeof(ifbr);
412 		ifd.ifd_data = &ifbr;
413 
414 		if (ioctl(s, SIOCSDRVSPEC, &ifd) < 0) {
415 			if (errno != EEXIST) {
416 				perror("Unable to add tap ifc to bridge!");
417 				exit(1);
418 			}
419 		}
420 	}
421 	close(s);
422 }
423 
424 /*
425  * Connect to the remote machine with ssh and set up a stream
426  */
427 static void
428 vknet_execssh(int fdin, int fdout, int compressOpt,
429 	      const char *remoteSide, const char *remoteBridge)
430 {
431 	char *remoteHost;
432 	char *remotePath;
433 	const char *av[24];
434 	int ac;
435 	pid_t pid;
436 
437 	/*
438 	 * Fork / parent returns.
439 	 */
440 	if ((pid = fork()) > 0)
441 		return;
442 	if (pid < 0) {
443 		perror("fork");
444 		exit(1);
445 	}
446 
447 	/*
448 	 * Setup stdin, stdout
449 	 */
450 	assert(fdin > 2);
451 	assert(fdout > 2);
452 	dup2(fdin, 0);
453 	dup2(fdout, 1);
454 	close(fdin);
455 	close(fdout);
456 
457 	/*
458 	 * Set up arguments
459 	 */
460 	remoteHost = strdup(remoteSide);
461 	if ((remotePath = strchr(remoteHost, ':')) != NULL) {
462 		*remotePath++ = 0;
463 	} else {
464 		remotePath = strdup("/var/run/vknet");
465 	}
466 	ac = 0;
467 	av[ac++] = "ssh";
468 	if (compressOpt)
469 		av[ac++] = "-C";
470 	av[ac++] = "-x";
471 	av[ac++] = "-T";
472 	av[ac++] = "-e";
473 	av[ac++] = "none";
474 	av[ac++] = remoteHost;
475 	av[ac++] = "exec";
476 	av[ac++] = "vknet";
477 	av[ac++] = "-S";
478 	if (remoteBridge) {
479 		av[ac++] = "-b";
480 		av[ac++] = remoteBridge;
481 	}
482 	av[ac++] = remotePath;
483 	av[ac++] = NULL;
484 	execv("/usr/bin/ssh", (void *)av);
485 }
486 
487 /*
488  * Misc
489  */
490 static
491 void
492 usage(void)
493 {
494 	fprintf(stderr,
495 		"vknet [-C] [-b local-bridge] [-B remote-bridge] [-r delay[:retries]]\n"
496 		"      local-spec [user@]remote[:remote-spec]\n"
497 		"vknet -S [-b local-bridge] local-spec\n"
498 	);
499 	exit(1);
500 }
501 
502