@@ -14,12 +14,14 @@
#include "qom/object_interfaces.h"
#include "sysemu/sysemu.h"
#include "sysemu/reset.h"
+#include "sysemu/runstate.h"
#include "sysemu/kvm.h"
#include "crypto/secret.h"
#include "crypto/hash.h"
#include "chardev/char.h"
#include "chardev/char-fe.h"
+#include "sysemu/vmi-intercept.h"
#include "sysemu/vmi-handshake.h"
#define HANDSHAKE_TIMEOUT_SEC 10
@@ -45,6 +47,10 @@ typedef struct VMIntrospection {
GSource *hsk_timer;
uint32_t handshake_timeout;
+ int intercepted_action;
+
+ int reconnect_time;
+
int64_t vm_start_time;
Notifier machine_ready;
@@ -59,6 +65,14 @@ typedef struct VMIntrospectionClass {
VMIntrospection *uniq;
} VMIntrospectionClass;
+static const char *action_string[] = {
+ "none",
+ "suspend",
+ "resume",
+};
+
+static bool suspend_pending;
+
#define TYPE_VM_INTROSPECTION "introspection"
#define VM_INTROSPECTION(obj) \
@@ -412,6 +426,39 @@ static bool connect_kernel(VMIntrospection *i, Error **errp)
return true;
}
+static void enable_socket_reconnect(VMIntrospection *i)
+{
+ if (i->sock_fd == -1 && i->reconnect_time) {
+ qemu_chr_fe_reconnect_time(&i->sock, i->reconnect_time);
+ qemu_chr_fe_disconnect(&i->sock);
+ i->reconnect_time = 0;
+ }
+}
+
+static void maybe_disable_socket_reconnect(VMIntrospection *i)
+{
+ if (i->reconnect_time == 0) {
+ info_report("VMI: disable socket reconnect");
+ i->reconnect_time = qemu_chr_fe_reconnect_time(&i->sock, 0);
+ }
+}
+
+static void continue_with_the_intercepted_action(VMIntrospection *i)
+{
+ switch (i->intercepted_action) {
+ case VMI_INTERCEPT_SUSPEND:
+ vm_stop(RUN_STATE_PAUSED);
+ break;
+ default:
+ error_report("VMI: %s: unexpected action %d",
+ __func__, i->intercepted_action);
+ break;
+ }
+
+ info_report("VMI: continue with '%s'",
+ action_string[i->intercepted_action]);
+}
+
/*
* We should read only the handshake structure,
* which might have a different size than what we expect.
@@ -495,6 +542,14 @@ static void chr_event_open(VMIntrospection *i)
{
Error *local_err = NULL;
+ if (suspend_pending) {
+ info_report("VMI: %s: too soon (suspend=%d)",
+ __func__, suspend_pending);
+ maybe_disable_socket_reconnect(i);
+ qemu_chr_fe_disconnect(&i->sock);
+ return;
+ }
+
if (!send_handshake_info(i, &local_err)) {
error_append_hint(&local_err, "reconnecting\n");
warn_report_err(local_err);
@@ -522,6 +577,15 @@ static void chr_event_close(VMIntrospection *i)
}
cancel_handshake_timer(i);
+
+ if (suspend_pending) {
+ maybe_disable_socket_reconnect(i);
+
+ if (i->intercepted_action != VMI_INTERCEPT_NONE) {
+ continue_with_the_intercepted_action(i);
+ i->intercepted_action = VMI_INTERCEPT_NONE;
+ }
+ }
}
static void chr_event(void *opaque, QEMUChrEvent event)
@@ -540,6 +604,89 @@ static void chr_event(void *opaque, QEMUChrEvent event)
}
}
+static VMIntrospection *vm_introspection_object(void)
+{
+ VMIntrospectionClass *ic;
+
+ ic = VM_INTROSPECTION_CLASS(object_class_by_name(TYPE_VM_INTROSPECTION));
+
+ return ic ? ic->uniq : NULL;
+}
+
+/*
+ * This ioctl succeeds only when KVM signals the introspection tool.
+ * (the socket is connected and the event was sent without error).
+ */
+static bool signal_introspection_tool_to_unhook(VMIntrospection *i)
+{
+ int err;
+
+ err = kvm_vm_ioctl(kvm_state, KVM_INTROSPECTION_PREUNHOOK, NULL);
+
+ return !err;
+}
+
+static bool record_intercept_action(VMI_intercept_command action)
+{
+ switch (action) {
+ case VMI_INTERCEPT_SUSPEND:
+ suspend_pending = true;
+ break;
+ case VMI_INTERCEPT_RESUME:
+ suspend_pending = false;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static bool intercept_action(VMIntrospection *i,
+ VMI_intercept_command action, Error **errp)
+{
+ if (i->intercepted_action != VMI_INTERCEPT_NONE) {
+ error_report("VMI: unhook in progress");
+ return false;
+ }
+
+ switch (action) {
+ case VMI_INTERCEPT_RESUME:
+ enable_socket_reconnect(i);
+ return false;
+ default:
+ break;
+ }
+
+ if (!signal_introspection_tool_to_unhook(i)) {
+ disconnect_and_unhook_kvmi(i);
+ return false;
+ }
+
+ i->intercepted_action = action;
+ return true;
+}
+
+bool vm_introspection_intercept(VMI_intercept_command action, Error **errp)
+{
+ VMIntrospection *i = vm_introspection_object();
+ bool intercepted = false;
+
+ info_report("VMI: intercept command: %s",
+ action < ARRAY_SIZE(action_string)
+ ? action_string[action]
+ : "unknown");
+
+ if (record_intercept_action(action) && i) {
+ intercepted = intercept_action(i, action, errp);
+ }
+
+ info_report("VMI: intercept action: %s",
+ intercepted ? "delayed" : "continue");
+
+ return intercepted;
+}
+
static void vm_introspection_reset(void *opaque)
{
VMIntrospection *i = opaque;
@@ -2,4 +2,5 @@ obj-$(call lnot,$(CONFIG_HAX)) += hax-stub.o
obj-$(call lnot,$(CONFIG_HVF)) += hvf-stub.o
obj-$(call lnot,$(CONFIG_WHPX)) += whpx-stub.o
obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o
+obj-$(call lnot,$(CONFIG_KVM)) += vmi-stubs.o
obj-$(call lnot,$(CONFIG_TCG)) += tcg-stub.o
new file mode 100644
@@ -0,0 +1,7 @@
+#include "qemu/osdep.h"
+#include "sysemu/vmi-intercept.h"
+
+bool vm_introspection_intercept(VMI_intercept_command ic, Error **errp)
+{
+ return false;
+}
new file mode 100644
@@ -0,0 +1,21 @@
+/*
+ * QEMU VM Introspection
+ *
+ * Copyright (C) 2018-2020 Bitdefender S.R.L.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef QEMU_VMI_INTERCEPT_H
+#define QEMU_VMI_INTERCEPT_H
+
+typedef enum {
+ VMI_INTERCEPT_NONE = 0,
+ VMI_INTERCEPT_SUSPEND,
+ VMI_INTERCEPT_RESUME,
+} VMI_intercept_command;
+
+bool vm_introspection_intercept(VMI_intercept_command ic, Error **errp);
+
+#endif /* QEMU_VMI_INTERCEPT_H */
@@ -39,6 +39,8 @@
#include "hw/mem/memory-device.h"
#include "hw/acpi/acpi_dev_interface.h"
+#include "sysemu/vmi-intercept.h"
+
NameInfo *qmp_query_name(Error **errp)
{
NameInfo *info = g_malloc0(sizeof(*info));
@@ -87,6 +89,9 @@ void qmp_stop(Error **errp)
if (runstate_check(RUN_STATE_INMIGRATE)) {
autostart = 0;
} else {
+ if (vm_introspection_intercept(VMI_INTERCEPT_SUSPEND, errp)) {
+ return;
+ }
vm_stop(RUN_STATE_PAUSED);
}
}
@@ -158,6 +163,11 @@ void qmp_cont(Error **errp)
autostart = 1;
} else {
vm_start();
+ /*
+ * this interception is post-event as we might need the vm to run before
+ * doing the interception, therefore we do not need the return value.
+ */
+ vm_introspection_intercept(VMI_INTERCEPT_RESUME, errp);
}
}