#include "vknet.h"
static void vknet_blastaway(ioinfo_t ios, ioinfo_t iod);
static void *vknet_stream(void *arg);
static void vknet_connect(ioinfo_t ios,
const char *localSide, const char *localBridge);
static pid_t vknet_execssh(int fdin, int fdout, int compressOpt,
const char *remoteSide, const char *remoteBridge);
static void usage(void);
static pthread_mutex_t MasterLock;
int
main(int ac, char **av)
{
int compressOpt = 0;
int remoteOpt = 0;
const char *localBridge = NULL;
const char *remoteBridge = NULL;
const char *localSide;
const char *remoteSide;
char *ptr;
int c;
int retriesOpt = -1;
int timeoutOpt = -1;
pid_t sshpid = -1;
pid_t p;
struct ioinfo ios;
struct ioinfo iod;
while ((c = getopt(ac, av, "b:B:r:CS")) != -1) {
switch (c) {
case 'b':
localBridge = optarg;
break;
case 'B':
remoteBridge = optarg;
break;
case 'r':
timeoutOpt = strtol(optarg, &ptr, 0);
if (ptr && *ptr == ':')
retriesOpt = strtol(ptr + 1, NULL, 0);
break;
case 'S':
remoteOpt = 1;
break;
case 'C':
compressOpt = 1;
break;
default:
usage();
}
}
av += optind;
ac -= optind;
if (remoteOpt) {
if (ac != 1)
usage();
localSide = av[0];
remoteSide = NULL;
} else {
if (ac != 2)
usage();
localSide = av[0];
remoteSide = av[1];
}
pthread_mutex_init(&MasterLock, NULL);
retry:
vknet_connect(&ios, localSide, localBridge);
if (remoteOpt) {
iod.fdin = 0;
iod.fdout = 1;
} else {
int fds[2];
if (pipe(fds) < 0) {
perror("pipe");
exit(1);
}
sshpid = vknet_execssh(fds[1], fds[1], compressOpt,
remoteSide, remoteBridge);
close(fds[1]);
iod.fdin = fds[0];
iod.fdout = fds[0];
}
vknet_blastaway(&ios, &iod);
if (sshpid > 0) {
if (kill(sshpid, SIGTERM) != 0)
perror("kill");
while ((p = waitpid(sshpid, NULL, 0)) != sshpid) {
if (p < 0 && errno != EINTR)
break;
}
sshpid = -1;
}
if (timeoutOpt >= 0 && retriesOpt != 0) {
printf("timeout %d retries %d\n", timeoutOpt, retriesOpt);
if (timeoutOpt > 0)
sleep(timeoutOpt);
if (retriesOpt > 0)
--retriesOpt;
goto retry;
}
exit(0);
}
static void
vknet_blastaway(ioinfo_t ios, ioinfo_t iod)
{
struct streaminfo stream1;
struct streaminfo stream2;
pthread_mutex_lock(&MasterLock);
stream1.fdin = ios->fdin;
stream1.fdout = iod->fdout;
stream1.flags = REBLOCK_OUT;
stream1.other = &stream2;
stream2.fdin = iod->fdin;
stream2.fdout = ios->fdout;
stream2.flags = REBLOCK_IN;
stream2.other = &stream1;
pthread_create(&stream1.thread, NULL, vknet_stream, &stream1);
pthread_create(&stream2.thread, NULL, vknet_stream, &stream2);
pthread_mutex_unlock(&MasterLock);
pthread_join(stream1.thread, NULL);
pthread_join(stream2.thread, NULL);
}
static
void *
vknet_stream(void *arg)
{
streaminfo_t stream = arg;
struct blkhead head;
u_int8_t *pkt;
int bytes;
int n;
int r;
pthread_mutex_lock(&MasterLock);
pthread_mutex_unlock(&MasterLock);
pkt = malloc(MAXPKT);
for (;;) {
if (stream->flags & REBLOCK_IN) {
bytes = sizeof(head);
for (n = 0; n < bytes; n += r) {
r = read(stream->fdin, (char *)&head + n,
bytes - n);
if (r <= 0)
break;
}
if (n != bytes)
break;
if (le32toh(head.magic) != MAGIC)
break;
bytes = le32toh(head.bytes);
if (bytes <= 0 || bytes > MAXPKT)
break;
for (n = 0; n < bytes; n += r) {
r = read(stream->fdin, pkt + n, bytes - n);
if (r <= 0)
break;
}
if (n != bytes)
break;
} else {
bytes = read(stream->fdin, pkt, MAXPKT);
if (bytes <= 0)
break;
}
if (stream->flags & REBLOCK_OUT) {
head.magic = htole32(MAGIC);
head.bytes = htole32(bytes);
if (write(stream->fdout, &head, sizeof(head)) != sizeof(head))
break;
if (write(stream->fdout, pkt, bytes) != bytes)
break;
} else {
if (write(stream->fdout, pkt, bytes) != bytes)
break;
}
}
free(pkt);
close(stream->fdin);
close(stream->fdout);
pthread_cancel(stream->other->thread);
pthread_exit(NULL);
}
static void
vknet_connect(ioinfo_t io, const char *localSide, const char *localBridge)
{
struct ifreq ifr;
struct ifaliasreq ifra;
char *buf = NULL;
int tap_fd;
int tap_unit;
int i;
int s;
int flags;
tap_unit = -1;
tap_fd = -1;
if (strcmp(localSide, "auto") == 0) {
for (i = 0; ; ++i) {
asprintf(&buf, "/dev/tap%d", i);
tap_fd = open(buf, O_RDWR | O_NONBLOCK);
free(buf);
if (tap_fd >= 0 || errno == ENOENT) {
tap_unit = i;
break;
}
}
} else if (strncmp(localSide, "tap", 3) == 0) {
asprintf(&buf, "/dev/%s", localSide);
tap_fd = open(buf, O_RDWR | O_NONBLOCK);
tap_unit = strtol(localSide + 3, NULL, 10);
free(buf);
} else if ((tap_fd = open(localSide, O_RDWR | O_NONBLOCK)) >= 0) {
const char *ptr = localSide + strlen(localSide);
while (ptr > localSide && ptr[-1] >= '0' && ptr[-1] <= '9')
--ptr;
tap_unit = strtol(ptr, NULL, 10);
} else {
struct sockaddr_un sunx;
int len;
snprintf(sunx.sun_path, sizeof(sunx.sun_path), "%s", localSide);
len = offsetof(struct sockaddr_un,
sun_path[strlen(sunx.sun_path)]);
++len;
sunx.sun_family = AF_UNIX;
sunx.sun_len = len;
tap_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (tap_fd >= 0) {
if (connect(tap_fd, (void *)&sunx, len) < 0) {
close(tap_fd);
tap_fd = -1;
}
}
}
if (tap_fd < 0) {
err(1, "Unable to connect to %s", localSide);
}
fcntl(tap_fd, F_SETFL, 0);
io->fdin = tap_fd;
io->fdout = tap_fd;
if (tap_unit < 0)
return;
bzero(&ifr, sizeof(ifr));
bzero(&ifra, sizeof(ifra));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tap%d", tap_unit);
snprintf(ifra.ifra_name, sizeof(ifra.ifra_name), "tap%d", tap_unit);
s = socket(AF_INET, SOCK_DGRAM, 0);
#if 0
if (SecureOpt) {
struct sockaddr_in *in;
in = (void *)&ifra.ifra_addr;
in->sin_family = AF_INET;
in->sin_len = sizeof(ifra.ifra_addr);
in->sin_addr = NetAddress;
in = (void *)&ifra.ifra_mask;
in->sin_family = AF_INET;
in->sin_len = sizeof(ifra.ifra_mask);
in->sin_addr = NetMask;
if (ioctl(s, SIOCAIFADDR, &ifra) < 0) {
perror("Unable to set address on tap interface");
exit(1);
}
}
#endif
flags = IFF_UP;
if (ioctl(s, SIOCGIFFLAGS, &ifr) >= 0) {
bzero(&ifr, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tap%d", tap_unit);
ifr.ifr_flags |= flags & 0xFFFF;
ifr.ifr_flagshigh |= flags >> 16;
if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) {
perror("Unable to set IFF_UP on tap interface");
exit(1);
}
}
if (localBridge) {
struct ifbreq ifbr;
struct ifdrv ifd;
bzero(&ifr, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", localBridge);
if (ioctl(s, SIOCIFCREATE, &ifr) < 0) {
if (errno != EEXIST) {
perror("Unable to create bridge interface");
exit(1);
}
}
bzero(&ifbr, sizeof(ifbr));
snprintf(ifbr.ifbr_ifsname, sizeof(ifbr.ifbr_ifsname),
"tap%d", tap_unit);
bzero(&ifd, sizeof(ifd));
snprintf(ifd.ifd_name, sizeof(ifd.ifd_name), "%s", localBridge);
ifd.ifd_cmd = BRDGADD;
ifd.ifd_len = sizeof(ifbr);
ifd.ifd_data = &ifbr;
if (ioctl(s, SIOCSDRVSPEC, &ifd) < 0) {
if (errno != EEXIST) {
perror("Unable to add tap ifc to bridge!");
exit(1);
}
}
}
close(s);
}
static pid_t
vknet_execssh(int fdin, int fdout, int compressOpt,
const char *remoteSide, const char *remoteBridge)
{
char *remoteHost;
char *remotePath;
const char *av[24];
int ac;
pid_t pid;
if ((pid = fork()) > 0)
return pid;
if (pid < 0) {
perror("fork");
exit(1);
}
assert(fdin > 2);
assert(fdout > 2);
dup2(fdin, 0);
dup2(fdout, 1);
close(fdin);
close(fdout);
remoteHost = strdup(remoteSide);
if ((remotePath = strchr(remoteHost, ':')) != NULL) {
*remotePath++ = 0;
} else {
remotePath = strdup("/var/run/vknet");
}
ac = 0;
av[ac++] = "ssh";
if (compressOpt)
av[ac++] = "-C";
av[ac++] = "-x";
av[ac++] = "-T";
av[ac++] = "-e";
av[ac++] = "none";
av[ac++] = remoteHost;
av[ac++] = "exec";
av[ac++] = "vknet";
av[ac++] = "-S";
if (remoteBridge) {
av[ac++] = "-b";
av[ac++] = remoteBridge;
}
av[ac++] = remotePath;
av[ac++] = NULL;
execv("/usr/bin/ssh", (void *)av);
exit(1);
}
static
void
usage(void)
{
fprintf(stderr,
"vknet [-C] [-b local-bridge] [-B remote-bridge] [-r delay[:retries]]\n"
" local-spec [user@]remote[:remote-spec]\n"
"vknet -S [-b local-bridge] local-spec\n"
);
exit(1);
}