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