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