libkrun/examples/external_kernel.c

321 lines
8.0 KiB
C

/*
* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <libkrun.h>
#include <getopt.h>
#include <stdbool.h>
#include <assert.h>
#include <pthread.h>
#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 (1024 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;
}