/* * This is an example implementing chroot-like functionality with libkrun. * * It executes the requested command (relative to NEWROOT) inside a fresh * Virtual Machine created and managed by libkrun. */ #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_ARGS_LEN 4096 #ifndef MAX_PATH #define MAX_PATH 4096 #endif enum net_mode { NET_MODE_PASST = 0, NET_MODE_TSI, }; #if defined(__x86_64__) #define KERNEL_FORMAT KRUN_KERNEL_FORMAT_ELF #else #define KERNEL_FORMAT KRUN_KERNEL_FORMAT_RAW #endif static void print_help(char *const name) { fprintf(stderr, "Usage: %s [OPTIONS] KERNEL\n" "OPTIONS: \n" " -b --boot-disk Path to a boot disk in raw format\n" " -c --kernel-cmdline Kernel command line\n" " -d --data-disk Path to a data disk in raw format\n" " -h --help Show help\n" " -i --initrd Path to initramfs\n" " -n --nested Enabled nested virtualization\n" " --net=NET_MODE Set network mode\n" " --passt-socket=PATH Connect to passt socket at PATH" "\n" "NET_MODE can be either TSI (default) or PASST\n" "\n" #if defined(__x86_64__) "KERNEL: path to the kernel image in ELF format\n", #else "KERNEL: path to the kernel image in RAW format\n", #endif name); } static const struct option long_options[] = { {"boot-disk", required_argument, NULL, 'b'}, {"kernel-cmdline", required_argument, NULL, 'c'}, {"data-disk", required_argument, NULL, 'd'}, {"initrd-path", required_argument, NULL, 'i'}, {"nested", no_argument, NULL, 'n'}, {"help", no_argument, NULL, 'h'}, {"passt-socket", required_argument, NULL, 'P'}, {NULL, 0, NULL, 0}}; struct cmdline { bool show_help; enum net_mode net_mode; char const *boot_disk; char const *data_disk; char const *passt_socket_path; char const *kernel_path; char const *kernel_cmdline; char const *initrd_path; bool nested; }; bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline) { assert(cmdline != NULL); // set the defaults *cmdline = (struct cmdline){ .show_help = false, .net_mode = NET_MODE_TSI, .passt_socket_path = "/tmp/network.sock", .boot_disk = NULL, .data_disk = NULL, .kernel_path = NULL, .kernel_cmdline = NULL, .initrd_path = NULL, .nested = false, }; int option_index = 0; int c; // the '+' in optstring is a GNU extension that disables permutating argv while ((c = getopt_long(argc, argv, "+hb:c:d:i:n", long_options, &option_index)) != -1) { switch (c) { case 'b': cmdline->boot_disk = optarg; break; case 'c': cmdline->kernel_cmdline = optarg; break; case 'd': cmdline->data_disk = optarg; break; case 'h': cmdline->show_help = true; return true; case 'i': cmdline->initrd_path = optarg; break; case 'n': cmdline->nested = true; break; case 'P': cmdline->passt_socket_path = optarg; break; case '?': return false; default: fprintf(stderr, "internal argument parsing error (returned character code 0x%x)\n", c); return false; } } if (optind <= argc - 1) { cmdline->kernel_path = argv[optind]; return true; } if (optind == argc) { fprintf(stderr, "Missing KERNEL argument\n"); } return false; } int start_passt() { int socket_fds[2]; const int PARENT = 0; const int CHILD = 1; if (socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds) < 0) { perror("Failed to create passt socket fd"); return -1; } int pid = fork(); if (pid < 0) { perror("fork"); return -1; } if (pid == 0) { // child if (close(socket_fds[PARENT]) < 0) { perror("close PARENT"); } char fd_as_str[16]; snprintf(fd_as_str, sizeof(fd_as_str), "%d", socket_fds[CHILD]); printf("passing fd %s to passt", fd_as_str); if (execlp("passt", "passt", "-f", "--fd", fd_as_str, NULL) < 0) { perror("execlp"); return -1; } } else { // parent if (close(socket_fds[CHILD]) < 0) { perror("close CHILD"); } return socket_fds[PARENT]; } } int main(int argc, char *const argv[]) { int ctx_id; int err; pthread_t thread; struct cmdline cmdline; if (!parse_cmdline(argc, argv, &cmdline)) { putchar('\n'); print_help(argv[0]); return -1; } if (cmdline.show_help) { print_help(argv[0]); return 0; } // Set the log level to "off". err = krun_set_log_level(0); if (err) { errno = -err; perror("Error configuring log level"); return -1; } // Create the configuration context. ctx_id = krun_create_ctx(); if (ctx_id < 0) { errno = -ctx_id; perror("Error creating configuration context"); return -1; } // Configure the number of vCPUs (2) and the amount of RAM (2048 MiB). if (err = krun_set_vm_config(ctx_id, 2, 2048)) { errno = -err; perror("Error configuring the number of vCPUs and/or the amount of RAM"); return -1; } if (cmdline.boot_disk) { if (err = krun_add_disk(ctx_id, "boot", cmdline.boot_disk, 0)) { errno = -err, perror("Error configuring boot disk"); return -1; } } if (cmdline.data_disk) { if (err = krun_add_disk(ctx_id, "data", cmdline.data_disk, 0)) { errno = -err, perror("Error configuring data disk"); return -1; } } if (cmdline.net_mode == NET_MODE_PASST) { uint8_t mac[] = {0x5a, 0x94, 0xef, 0xe4, 0x0c, 0xee}; if (cmdline.passt_socket_path != NULL) { if (err = krun_add_net_unixstream(ctx_id, cmdline.passt_socket_path, -1, &mac[0], COMPAT_NET_FEATURES, 0)) { errno = -err; perror("Error configuring net mode"); return -1; } } else { int passt_fd = start_passt(); if (passt_fd < 0) { return -1; } if (err = krun_add_net_unixstream(ctx_id, NULL, passt_fd, &mac[0], COMPAT_NET_FEATURES, 0)) { errno = -err; perror("Error configuring net mode"); return -1; } } } fprintf(stderr, "kernel_path: %s\n", cmdline.kernel_path); fprintf(stderr, "kernel_cmdline: %s\n", cmdline.kernel_cmdline); fflush(stderr); if (err = krun_set_kernel(ctx_id, cmdline.kernel_path, KERNEL_FORMAT, cmdline.initrd_path, cmdline.kernel_cmdline)) { errno = -err; perror("Error configuring kernel"); return -1; } fprintf(stderr, "nested=%d\n", cmdline.nested); if (err = krun_set_nested_virt(ctx_id, cmdline.nested)) { errno = -err; perror("Error configuring nested virtualization"); return -1; } // Start and enter the microVM. Unless there is some error while creating the microVM // this function never returns. if (err = krun_start_enter(ctx_id)) { errno = -err; perror("Error creating the microVM"); return -1; } // Not reached. return 0; }