/* */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hvsock.h" #include "protocol.h" int daemon_flag = 0; int listen_flag = 0; int connect_flag = 0; char *default_sid = "30D48B34-7D27-4B0B-AAAF-BBBED334DD59"; void fatal(const char *msg) { syslog(LOG_CRIT, "%s Error: %d. %s", msg, errno, strerror(errno)); exit(1); } int alloc_tap(const char *dev) { int fd; struct ifreq ifr; const char *clonedev = "/dev/net/tun"; if ((fd = open(clonedev, O_RDWR)) == -1) { fatal("Failed to open /dev/net/tun"); } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TAP | IFF_NO_PI; strncpy(ifr.ifr_name, dev, IFNAMSIZ); if (ioctl(fd, TUNSETIFF, (void*) &ifr) < 0) { fatal("TUNSETIFF failed"); } int persist = 1; if (ioctl(fd, TUNSETPERSIST, persist) < 0) { fatal("TUNSETPERSIST failed"); } syslog(LOG_INFO, "successfully created TAP device %s", dev); return fd; } void set_macaddr(const char *dev, uint8_t *mac) { int fd; struct ifreq ifq; fd = socket(PF_INET, SOCK_DGRAM, 0); strcpy(ifq.ifr_name, dev); memcpy(&ifq.ifr_hwaddr.sa_data[0], mac, 6); ifq.ifr_hwaddr.sa_family = ARPHRD_ETHER; if (ioctl(fd, SIOCSIFHWADDR, &ifq) == -1) { fatal("SIOCSIFHWADDR failed"); } close(fd); } /* Negotiate a vmnet connection, returns 0 on success and 1 on error. */ int negotiate(int fd, struct vif_info *vif) { /* Negotiate with com.docker.slirp */ struct init_message *me = create_init_message(); if (write_init_message(fd, me) == -1) { goto err; } struct init_message you; if (read_init_message(fd, &you) == -1) { goto err; } char *txt = print_init_message(&you); syslog(LOG_INFO, "Server reports %s", txt); free(txt); enum command command = ethernet; if (write_command(fd, &command) == -1) { goto err; } struct ethernet_args args; /* We don't need a uuid */ memset(&args.uuid_string[0], 0, sizeof(args.uuid_string)); if (write_ethernet_args(fd, &args) == -1) { goto err; } if (read_vif_info(fd, vif) == -1) { goto err; } return 0; err: syslog(LOG_CRIT, "Failed to negotiate vmnet connection"); return 1; } /* Argument passed to proxy threads */ struct connection { int fd; /* Hyper-V socket with vmnet protocol */ int tapfd; /* TAP device with ethernet frames */ struct vif_info vif; /* Contains VIF MAC, MTU etc, received from server */ }; static void* vmnet_to_tap(void *arg) { int length, n; struct connection *connection = (struct connection*) arg; uint8_t header[2]; uint8_t buffer[2048]; for (;;) { if (really_read(connection->fd, &header[0], 2) == -1){ fatal("Failed to read a packet header from host"); } length = (header[0] & 0xff) | ((header[1] & 0xff) << 8); if (length > sizeof(buffer)) { syslog(LOG_CRIT, "Received an over-large packet: %d > %ld", length, sizeof(buffer)); exit(1); } if (really_read(connection->fd, &buffer[0], length) == -1){ syslog(LOG_CRIT, "Failed to read packet contents from host"); exit(1); } n = write(connection->tapfd, &buffer[0], length); if (n != length) { syslog(LOG_CRIT, "Failed to write %d bytes to tap device (wrote %d)", length, n); exit(1); } } } static void* tap_to_vmnet(void *arg) { int length; struct connection *connection = (struct connection*) arg; uint8_t header[2]; uint8_t buffer[2048]; for (;;) { length = read(connection->tapfd, &buffer[0], sizeof(buffer)); if (length == -1) { if (errno == ENXIO) { fatal("tap device has gone down"); } syslog(LOG_WARNING, "ignoring error %d", errno); /* This is what mirage-net-unix does. Is it a good idea really? */ continue; } header[0] = (length >> 0) & 0xff; header[1] = (length >> 8) & 0xff; if (really_write(connection->fd, &header[0], 2) == -1){ fatal("Failed to write packet header"); } if (really_write(connection->fd, &buffer[0], length) == -1) { fatal("Failed to write packet body"); } } return NULL; } /* Handle a connection by exchanging ethernet frames forever. */ static void handle(struct connection *connection) { pthread_t v2t, t2v; if (pthread_create(&v2t, NULL, vmnet_to_tap, connection) != 0){ fatal("Failed to create the vmnet_to_tap thread"); } if (pthread_create(&t2v, NULL, tap_to_vmnet, connection) != 0){ fatal("Failed to create the tap_to_vmnet thread"); } if (pthread_join(v2t, NULL) != 0){ fatal("Failed to join the vmnet_to_tap thread"); } if (pthread_join(t2v, NULL) != 0){ fatal("Failed to join the tap_to_vmnet thread"); } } static int create_listening_socket(GUID serviceid) { int lsock = -1; SOCKADDR_HV sa; int res; lsock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW); if (lsock == -1) { fatal("socket()"); } sa.Family = AF_HYPERV; sa.Reserved = 0; sa.VmId = HV_GUID_WILDCARD; sa.ServiceId = serviceid; res = bind(lsock, (const struct sockaddr *)&sa, sizeof(sa)); if (res == -1) { fatal("bind()"); } res = listen(lsock, SOMAXCONN); if (res == -1) { fatal("listen()"); } return lsock; } static int connect_socket(GUID serviceid) { int sock = -1; SOCKADDR_HV sa; int res; sock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW); if (sock == -1) { fatal("socket()"); } sa.Family = AF_HYPERV; sa.Reserved = 0; sa.VmId = HV_GUID_PARENT; sa.ServiceId = serviceid; res = connect(sock, (const struct sockaddr *)&sa, sizeof(sa)); if (res == -1) { fatal("connect()"); } return sock; } static int accept_socket(int lsock) { int csock = -1; SOCKADDR_HV sac; socklen_t socklen = sizeof(sac); csock = accept(lsock, (struct sockaddr *)&sac, &socklen); if (csock == -1) { fatal("accept()"); } syslog(LOG_INFO, "Connect from: "GUID_FMT":"GUID_FMT"\n", GUID_ARGS(sac.VmId), GUID_ARGS(sac.ServiceId)); return csock; } void write_pidfile(const char *pidfile) { pid_t pid = getpid(); char * pid_s; FILE *file; int len; if (asprintf(&pid_s, "%lld", (long long) pid) == -1) { fatal("Failed to allocate pidfile string"); } len = strlen(pid_s); file = fopen(pidfile, "w"); if (file == NULL) { syslog(LOG_CRIT, "Failed to open pidfile %s", pidfile); exit(1); } if (fwrite(pid_s, 1, len, file) != len) { fatal("Failed to write pid to pidfile"); } fclose(file); free(pid_s); } void daemonize(const char *pidfile){ pid_t pid = fork (); if (pid == -1) { fatal("Failed to fork()"); } else if (pid != 0) exit(0); if (setsid () == -1) { fatal("Failed to setsid()"); } if (chdir ("/") == -1) { fatal("Failed to chdir()"); } int null = open("/dev/null", O_RDWR); dup2(null, STDIN_FILENO); dup2(null, STDOUT_FILENO); dup2(null, STDERR_FILENO); close(null); if (pidfile) write_pidfile(pidfile); } void usage(char *name) { printf("%s usage:\n", name); printf("\t[--daemon] [--tap ] [--serviceid ] [--pid ]\n"); printf("\t[--listen | --connect]\n\n"); printf("where\n"); printf("\t--daemonize: run as a background daemon\n"); printf("\t--tap : create a tap device with the given name\n"); printf("\t (defaults to eth1)\n"); printf("\t--serviceid : use as the well-known service GUID\n"); printf("\t (defaults to %s)\n", default_sid); printf("\t--pid : write a pid to the given file\n"); printf("\t--listen: listen forever for incoming AF_HVSOCK connections\n"); printf("\t--connect: connect to the parent partition\n"); } int main(int argc, char **argv) { int res = 0; GUID sid; int c; /* Defaults to a testing GUID */ char *serviceid = default_sid; char *tap = "eth1"; char *pidfile = NULL; opterr = 0; while (1) { static struct option long_options[] = { /* These options set a flag. */ {"daemon", no_argument, &daemon_flag, 1}, {"serviceid", required_argument, NULL, 's'}, {"tap", required_argument, NULL, 't'}, {"pidfile", required_argument, NULL, 'p'}, {"listen", no_argument, &listen_flag, 1}, {"connect", no_argument, &connect_flag, 1}, {0, 0, 0, 0} }; int option_index = 0; c = getopt_long (argc, argv, "ds:t:p:", long_options, &option_index); if (c == -1) break; switch (c) { case 'd': daemon_flag = 1; break; case 's': serviceid = optarg; break; case 't': tap = optarg; break; case 'p': pidfile = optarg; break; case 0: break; default: usage (argv[0]); exit (1); } } if ((listen_flag && connect_flag) || !(listen_flag || connect_flag)){ fprintf(stderr, "Please supply either the --listen or --connect flag, but not both.\n"); exit(1); } if (daemon_flag && !pidfile){ fprintf(stderr, "For daemon mode, please supply a --pidfile argument.\n"); exit(1); } res = parseguid(serviceid, &sid); if (res) { fprintf(stderr, "Failed to parse serviceid as GUID: %s\n", serviceid); usage(argv[0]); exit(1); } int log_flags = LOG_CONS | LOG_NDELAY; if (!daemon_flag) { log_flags |= LOG_PERROR; } openlog(argv[0], log_flags, LOG_DAEMON); int tapfd = alloc_tap(tap); struct connection connection; connection.tapfd = tapfd; int sock = -1; for (;;) { if (sock != -1) { close(sock); sock = -1; } if (listen_flag) { syslog(LOG_INFO, "starting in listening mode with serviceid=%s and tap=%s", serviceid, tap); int lsocket = create_listening_socket(sid); sock = accept_socket(lsocket); close(lsocket); } else { syslog(LOG_INFO, "starting in connect mode with serviceid=%s and tap=%s", serviceid, tap); sock = connect_socket(sid); } connection.fd = sock; if (negotiate(sock, &connection.vif) != 0) { sleep(1); continue; } syslog(LOG_INFO, "VMNET VIF has MAC %02x:%02x:%02x:%02x:%02x:%02x", connection.vif.mac[0], connection.vif.mac[1], connection.vif.mac[2], connection.vif.mac[3], connection.vif.mac[4], connection.vif.mac[5] ); set_macaddr(tap, &connection.vif.mac[0]); /* Daemonize after we've made our first reliable connection */ if (daemon_flag) { daemon_flag = 0; daemonize(pidfile); } handle(&connection); } }