Add (partial) support for agentx in vmd.
authormartijn <martijn@openbsd.org>
Tue, 13 Sep 2022 10:28:19 +0000 (10:28 +0000)
committermartijn <martijn@openbsd.org>
Tue, 13 Sep 2022 10:28:19 +0000 (10:28 +0000)
Metrics can be found under mib-2.236 and VM-MIB (RFC7666).

Stress tested by and happy noises from Mischa Peters
OK dv@

usr.sbin/vmd/Makefile
usr.sbin/vmd/parse.y
usr.sbin/vmd/proc.h
usr.sbin/vmd/vm.conf.5
usr.sbin/vmd/vm_agentx.c [new file with mode: 0644]
usr.sbin/vmd/vmd.c
usr.sbin/vmd/vmd.h

index 0abaf00..42e94fd 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.26 2021/04/05 18:09:48 dv Exp $
+#      $OpenBSD: Makefile,v 1.27 2022/09/13 10:28:19 martijn Exp $
 
 .if ${MACHINE} == "amd64"
 
@@ -7,6 +7,7 @@ SRCS=           vmd.c control.c log.c priv.c proc.c config.c vmm.c
 SRCS+=         vm.c loadfile_elf.c pci.c virtio.c i8259.c mc146818.c
 SRCS+=         ns8250.c i8253.c dhcp.c packet.c
 SRCS+=         parse.y atomicio.c vioscsi.c vioraw.c vioqcow2.c fw_cfg.c
+SRCS+=         vm_agentx.c
 
 CFLAGS+=       -Wall -I${.CURDIR}
 CFLAGS+=       -Wstrict-prototypes -Wmissing-prototypes
@@ -14,8 +15,8 @@ CFLAGS+=      -Wmissing-declarations
 CFLAGS+=       -Wshadow -Wpointer-arith -Wcast-qual
 CFLAGS+=       -Wsign-compare
 
-LDADD+=                -lutil -lpthread -levent -lz
-DPADD+=                ${LIBUTIL} ${LIBPTHREAD} ${LIBEVENT} ${LIBZ}
+LDADD+=                -lutil -lpthread -levent -lz -lagentx
+DPADD+=                ${LIBUTIL} ${LIBPTHREAD} ${LIBEVENT} ${LIBZ} ${LIBAGENTX}
 
 YFLAGS=
 
index f8aebbe..3175e17 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.61 2022/05/04 23:17:25 dv Exp $   */
+/*     $OpenBSD: parse.y,v 1.62 2022/09/13 10:28:19 martijn Exp $      */
 
 /*
  * Copyright (c) 2007-2016 Reyk Floeter <reyk@openbsd.org>
@@ -34,6 +34,7 @@
 #include <netinet/in.h>
 #include <netinet/if_ether.h>
 
+#include <agentx.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <limits.h>
@@ -119,7 +120,8 @@ typedef struct {
 
 
 %token INCLUDE ERROR
-%token ADD ALLOW BOOT CDROM DEVICE DISABLE DISK DOWN ENABLE FORMAT GROUP
+%token ADD AGENTX ALLOW BOOT CDROM CONTEXT DEVICE DISABLE DISK DOWN ENABLE
+%token FORMAT GROUP
 %token INET6 INSTANCE INTERFACE LLADDR LOCAL LOCKED MEMORY NET NIFS OWNER
 %token PATH PREFIX RDOMAIN SIZE SOCKET SWITCH UP VM VMID STAGGERED START
 %token  PARALLEL DELAY
@@ -217,6 +219,18 @@ main               : LOCAL INET6 {
                        env->vmd_ps.ps_csock.cs_uid = $3.uid;
                        env->vmd_ps.ps_csock.cs_gid = $3.gid == -1 ? 0 : $3.gid;
                }
+               | AGENTX {
+                       env->vmd_cfg.cfg_agentx.ax_enabled = 1;
+               } agentxopts {
+                       if (env->vmd_cfg.cfg_agentx.ax_path[0] == '\0')
+                               if (strlcpy(env->vmd_cfg.cfg_agentx.ax_path,
+                                   AGENTX_MASTER_PATH,
+                                   sizeof(env->vmd_cfg.cfg_agentx.ax_path)) >=
+                                   sizeof(env->vmd_cfg.cfg_agentx.ax_path)) {
+                                       yyerror("invalid agentx path");
+                                       YYERROR;
+                               }
+               }
                | STAGGERED START PARALLEL NUMBER DELAY NUMBER {
                        env->vmd_cfg.cfg_flags |= VMD_CFG_STAGGERED_START;
                        env->vmd_cfg.delay.tv_sec = $6;
@@ -595,6 +609,35 @@ owner_id   : NUMBER                {
                }
                ;
 
+agentxopt      : CONTEXT STRING {
+                       if (strlcpy(env->vmd_cfg.cfg_agentx.ax_context, $2,
+                           sizeof(env->vmd_cfg.cfg_agentx.ax_context)) >=
+                           sizeof(env->vmd_cfg.cfg_agentx.ax_context)) {
+                               yyerror("agentx context too large");
+                               free($2);
+                               YYERROR;
+                       }
+                       free($2);
+               }
+               | PATH STRING {
+                       if (strlcpy(env->vmd_cfg.cfg_agentx.ax_path, $2,
+                           sizeof(env->vmd_cfg.cfg_agentx.ax_path)) >=
+                           sizeof(env->vmd_cfg.cfg_agentx.ax_path)) {
+                               yyerror("agentx path too large");
+                               free($2);
+                               YYERROR;
+                       }
+                       free($2);
+                       if (env->vmd_cfg.cfg_agentx.ax_path[0] != '/') {
+                               yyerror("agentx path is not absolute");
+                               YYERROR;
+                       }
+               }
+
+agentxopts     : /* none */
+               | agentxopts agentxopt
+               ;
+
 image_format   : /* none       */      {
                        $$ = 0;
                }
@@ -769,9 +812,11 @@ lookup(char *s)
        /* this has to be sorted always */
        static const struct keywords keywords[] = {
                { "add",                ADD },
+               { "agentx",             AGENTX },
                { "allow",              ALLOW },
                { "boot",               BOOT },
                { "cdrom",              CDROM },
+               { "context",            CONTEXT},
                { "delay",              DELAY },
                { "device",             DEVICE },
                { "disable",            DISABLE },
@@ -793,6 +838,7 @@ lookup(char *s)
                { "net",                NET },
                { "owner",              OWNER },
                { "parallel",           PARALLEL },
+               { "path",               PATH },
                { "prefix",             PREFIX },
                { "rdomain",            RDOMAIN },
                { "size",               SIZE },
@@ -1149,6 +1195,10 @@ parse_config(const char *filename)
        /* Set the default switch type */
        (void)strlcpy(vsw_type, VMD_SWITCH_TYPE, sizeof(vsw_type));
 
+       env->vmd_cfg.cfg_agentx.ax_enabled = 0;
+       env->vmd_cfg.cfg_agentx.ax_context[0] = '\0';
+       env->vmd_cfg.cfg_agentx.ax_path[0] = '\0';
+
        yyparse();
        errors = file->errors;
        popfile();
index dba9972..123df4f 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: proc.h,v 1.20 2021/06/16 16:55:02 dv Exp $    */
+/*     $OpenBSD: proc.h,v 1.21 2022/09/13 10:28:19 martijn Exp $       */
 
 /*
  * Copyright (c) 2010-2015 Reyk Floeter <reyk@openbsd.org>
@@ -79,6 +79,7 @@ TAILQ_HEAD(ctl_connlist, ctl_conn);
 enum privsep_procid {
        PROC_PARENT     = 0,
        PROC_CONTROL,
+       PROC_AGENTX,
        PROC_VMM,
        PROC_PRIV,
        PROC_MAX,
index d63a389..ca39b14 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: vm.conf.5,v 1.58 2022/08/04 11:50:51 stsp Exp $
+.\" $OpenBSD: vm.conf.5,v 1.59 2022/09/13 10:28:19 martijn Exp $
 .\"
 .\" Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
 .\" Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: August 4 2022 $
+.Dd $Mdocdate: September 13 2022 $
 .Dt VM.CONF 5
 .Os
 .Sh NAME
@@ -91,6 +91,19 @@ vm "vm1.example.com" {
 .Sh GLOBAL CONFIGURATION
 The following setting can be configured globally:
 .Bl -tag -width Ds
+.It Ic agentx Oo Ic context Ar context Oc Oo Ic path Ar path Oc
+Export vm metrics via an AgentX compatible
+.Pq snmp
+daemon by connecting to
+.Ar path .
+Metrics can be found under the vmMIB subtree
+.Pq mib-2.236 .
+If
+.Ar path
+is omitted it will default to
+.Pa /var/agentx/master .
+.Ar Context
+is the SNMPv3 context and can usually be omitted.
 .It Ic local prefix Ar address Ns Li / Ns Ar prefix
 Set the network prefix that is used to allocate subnets for
 local interfaces, see
diff --git a/usr.sbin/vmd/vm_agentx.c b/usr.sbin/vmd/vm_agentx.c
new file mode 100644 (file)
index 0000000..e54133f
--- /dev/null
@@ -0,0 +1,538 @@
+/*     $OpenBSD: vm_agentx.c,v 1.1 2022/09/13 10:28:19 martijn Exp $ */
+
+/*
+ * Copyright (c) 2022 Martijn van Duren <martijn@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/un.h>
+
+#include <agentx.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "proc.h"
+#include "vmd.h"
+
+struct conn {
+       struct event ev;
+       struct agentx *agentx;
+};
+
+void vm_agentx_run(struct privsep *, struct privsep_proc *, void *);
+int vm_agentx_dispatch_parent(int, struct privsep_proc *, struct imsg *);
+void vm_agentx_configure(struct vmd_agentx *);
+static void vm_agentx_nofd(struct agentx *, void *, int);
+static void vm_agentx_tryconnect(int, short, void *);
+static void vm_agentx_read(int, short, void *);
+static void vm_agentx_flush_pending(void);
+static int vm_agentx_sortvir(const void *, const void *);
+static int vm_agentx_adminstate(int);
+static int vm_agentx_operstate(int);
+static void vm_agentx_vmHvSoftware(struct agentx_varbind *);
+static void vm_agentx_vmHvVersion(struct agentx_varbind *);
+static void vm_agentx_vmHvObjectID(struct agentx_varbind *);
+static void vm_agentx_vminfo(struct agentx_varbind *);
+
+static struct agentx_index *vmIndex;
+static struct agentx_object *vmNumber, *vmName, *vmUUID, *vmOSType;
+static struct agentx_object *vmAdminState, *vmOperState, *vmAutoStart;
+static struct agentx_object *vmPersistent, *vmCurCpuNumber, *vmMinCpuNumber;
+static struct agentx_object *vmMaxCpuNumber, *vmMemUnit, *vmCurMem, *vmMinMem;
+static struct agentx_object *vmMaxMem;
+
+static struct vmd_agentx *vmd_agentx;
+
+static struct agentx_varbind **vminfo = NULL;
+static size_t vminfolen = 0;
+static size_t vminfosize = 0;
+static int vmcollecting = 0;
+
+#define VMMIB AGENTX_MIB2, 236
+#define VMOBJECTS VMMIB, 1
+#define VMHVSOFTWARE VMOBJECTS, 1, 1
+#define VMHVVERSION VMOBJECTS, 1, 2
+#define VMHVOBJECTID VMOBJECTS, 1, 3
+#define VMNUMBER VMOBJECTS, 2
+#define VMENTRY VMOBJECTS, 4, 1
+#define VMINDEX VMENTRY, 1
+#define VMNAME VMENTRY, 2
+#define VMUUID VMENTRY, 3
+#define VMOSTYPE VMENTRY, 4
+#define VMADMINSTATE VMENTRY, 5
+#define VMOPERSTATE VMENTRY, 6
+#define VMAUTOSTART VMENTRY, 7
+#define VMPERSISTENT VMENTRY, 8
+#define VMCURCPUNUMBER VMENTRY, 9
+#define VMMINCPUNUMBER VMENTRY, 10
+#define VMMAXCPUNUMBER VMENTRY, 11
+#define VMMEMUNIT VMENTRY, 12
+#define VMCURMEM VMENTRY, 13
+#define VMMINMEM VMENTRY, 14
+#define VMMAXMEM VMENTRY, 15
+
+#define AGENTX_GROUP "_agentx"
+#define MEM_SCALE (1024 * 1024)
+
+static struct privsep_proc procs[] = {
+       { "parent",     PROC_PARENT,    vm_agentx_dispatch_parent  },
+};
+
+void
+vm_agentx(struct privsep *ps, struct privsep_proc *p)
+{
+       struct group *grp;
+
+       /*
+        * Make sure we can connect to /var/agentx/master with the correct
+        * group permissions.
+        */
+       if ((grp = getgrnam(AGENTX_GROUP)) == NULL)
+               fatal("getgrnam");
+       ps->ps_pw->pw_gid = grp->gr_gid;
+
+       proc_run(ps, p, procs, nitems(procs), vm_agentx_run, NULL);
+}
+
+void
+vm_agentx_shutdown(void)
+{
+}
+
+void
+vm_agentx_run(struct privsep *ps, struct privsep_proc *p, void *arg)
+{
+       /*
+        * pledge in agentx process
+        * stdio - for malloc and basic I/O including events.
+        * recvfd - for the proc fd exchange.
+        * unix - for access to the agentx master socket.
+        */
+       if (pledge("stdio recvfd unix", NULL) == -1)
+               fatal("pledge");
+}
+
+int
+vm_agentx_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+       static struct vmop_info_result *vir = NULL;
+       static struct vmop_info_result *mvir = NULL;
+       struct vmd *env = p->p_ps->ps_env; 
+       struct vmop_info_result *tvir;
+       struct agentx_object *reqObject;
+       static size_t nvir = 0;
+       static size_t virlen = 0;
+       static int error = 0;
+       size_t i, j, index;
+       enum agentx_request_type rtype;
+
+       switch (imsg->hdr.type) {
+       case IMSG_VMDOP_GET_INFO_VM_DATA:
+               if (error)
+                       break;
+               if (nvir + 1 > virlen) {
+                       tvir = reallocarray(vir, virlen + 10, sizeof(*vir));
+                       if (tvir == NULL) {
+                               log_warn("%s: Couldn't dispatch vm information",
+                                   __func__);
+                               error = 1;
+                               break;
+                       }
+                       virlen += 10;
+                       vir = tvir;
+               }
+                memcpy(&(vir[nvir++]), imsg->data, sizeof(vir[nvir]));
+               break;
+       case IMSG_VMDOP_GET_INFO_VM_END_DATA:
+               vmcollecting = 0;
+               if (error) {
+                       for (i = 0; i < vminfolen; i++)
+                               agentx_varbind_error(vminfo[i]);
+                       vminfolen = 0;
+                       error = 0;
+                       nvir = 0;
+                       return (0);
+               }
+
+               qsort(vir, nvir, sizeof(*vir), vm_agentx_sortvir);
+               for (i = 0; i < vminfolen; i++) {
+                       reqObject = agentx_varbind_get_object(vminfo[i]);
+                       if (reqObject == vmNumber) {
+                               agentx_varbind_integer(vminfo[i],
+                                   (int32_t)nvir);
+                               continue;
+                       }
+                       index = agentx_varbind_get_index_integer(vminfo[i],
+                           vmIndex);
+                       rtype = agentx_varbind_request(vminfo[i]);
+                       for (j = 0; j < nvir; j++) {
+                               if (vir[j].vir_info.vir_id < index)
+                                       continue;
+                               if (vir[j].vir_info.vir_id == index &&
+                                   (rtype == AGENTX_REQUEST_TYPE_GET ||
+                                   rtype ==
+                                   AGENTX_REQUEST_TYPE_GETNEXTINCLUSIVE))
+                                       break;
+                               if (vir[j].vir_info.vir_id > index &&
+                                   (rtype == AGENTX_REQUEST_TYPE_GETNEXT ||
+                                   rtype ==
+                                   AGENTX_REQUEST_TYPE_GETNEXTINCLUSIVE))
+                                       break;
+                       }
+                       if (j == nvir) {
+                               agentx_varbind_notfound(vminfo[i]);
+                               continue;
+                       }
+                       mvir = &(vir[j]);
+                       agentx_varbind_set_index_integer(vminfo[i], vmIndex,
+                           mvir->vir_info.vir_id);
+                       if (reqObject == vmName)
+                               agentx_varbind_string(vminfo[i],
+                                   mvir->vir_info.vir_name);
+                       else if (reqObject == vmUUID)
+                               agentx_varbind_string(vminfo[i], "");
+                       else if (reqObject == vmOSType)
+                               agentx_varbind_string(vminfo[i], "");
+                       else if (reqObject == vmAdminState)
+                               agentx_varbind_integer(vminfo[i],
+                                   vm_agentx_adminstate(mvir->vir_state));
+                       else if (reqObject == vmOperState)
+                               agentx_varbind_integer(vminfo[i],
+                                   vm_agentx_operstate(mvir->vir_state));
+                       else if (reqObject == vmAutoStart)
+                               agentx_varbind_integer(vminfo[i],
+                                   mvir->vir_state & VM_STATE_DISABLED ?
+                                   3 : 2);
+/* XXX We can dynamically create vm's but I don't know how to differentiate */
+                       else if (reqObject == vmPersistent)
+                               agentx_varbind_integer(vminfo[i], 1);
+/* We currently only support a single CPU */
+                       else if (reqObject == vmCurCpuNumber ||
+                           reqObject == vmMinCpuNumber ||
+                           reqObject == vmMaxCpuNumber)
+                               agentx_varbind_integer(vminfo[i],
+                                   mvir->vir_info.vir_ncpus);
+                       else if (reqObject == vmMemUnit)
+                               agentx_varbind_integer(vminfo[i], MEM_SCALE);
+                       else if (reqObject == vmCurMem)
+                               agentx_varbind_integer(vminfo[i],
+                                   mvir->vir_info.vir_used_size / MEM_SCALE);
+                       else if (reqObject == vmMinMem)
+                               agentx_varbind_integer(vminfo[i], -1);
+                       else if (reqObject == vmMaxMem)
+                               agentx_varbind_integer(vminfo[i],
+                                   mvir->vir_info.vir_memory_size / MEM_SCALE);
+/* We probably had a reload */
+                       else
+                               agentx_varbind_notfound(vminfo[i]);
+               }
+               vminfolen = 0;
+               nvir = 0;
+               break;
+       case IMSG_VMDOP_CONFIG:
+               config_getconfig(env, imsg);
+               vm_agentx_configure(&env->vmd_cfg.cfg_agentx);
+               break;
+       default:
+               return (-1);
+       }
+       return (0);
+}
+
+void
+vm_agentx_configure(struct vmd_agentx *env)
+{
+       static char curpath[sizeof(env->ax_path)];
+       static char curcontext[sizeof(env->ax_context)];
+       static struct conn *conn;
+       static struct agentx_session *sess;
+       static struct agentx_context *ctx;
+       struct agentx_region *vmMIB;
+       char *context = env->ax_context[0] == '\0' ? NULL : env->ax_context;
+       int changed = 0;
+
+       vmd_agentx = env;
+
+       agentx_log_fatal = fatalx;
+       agentx_log_warn = log_warnx;
+       agentx_log_info = log_info;
+       agentx_log_debug = log_debug;
+
+       if (!vmd_agentx->ax_enabled) {
+               if (conn != NULL) {
+                       agentx_free(conn->agentx);
+                       conn = NULL;
+                       sess = NULL;
+                       ctx = NULL;
+                       vm_agentx_flush_pending();
+               }
+               return;
+       }
+
+       if (strcmp(curpath, vmd_agentx->ax_path) != 0 || conn == NULL) {
+               if (conn != NULL) {
+                       agentx_free(conn->agentx);
+                       conn = NULL;
+                       sess = NULL;
+                       ctx = NULL;
+                       vm_agentx_flush_pending();
+               }
+
+               if ((conn = malloc(sizeof(*conn))) == NULL)
+                       fatal(NULL);
+               /* Set to something so we can safely event_del */
+               evtimer_set(&conn->ev, vm_agentx_tryconnect, conn);
+               /* result assigned in vm_agentx_nofd */
+               if (agentx(vm_agentx_nofd, conn) == NULL)
+                       fatal("Can't setup agentx");
+               sess = agentx_session(conn->agentx, NULL, 0, "vmd", 0);
+               if (sess == NULL)
+                       fatal("Can't setup agentx session");
+               (void) strlcpy(curpath, vmd_agentx->ax_path, sizeof(curpath));
+               changed = 1;
+       }
+
+       if (strcmp(curcontext, vmd_agentx->ax_context) != 0 || changed) {
+               if (!changed) {
+                       agentx_context_free(ctx);
+                       vm_agentx_flush_pending();
+               }
+               if ((ctx = agentx_context(sess, context)) == NULL)
+                       fatal("Can't setup agentx context");
+               strlcpy(curcontext, vmd_agentx->ax_context, sizeof(curcontext));
+               changed = 1;
+       }
+
+       if (!changed)
+               return;
+
+       if ((vmMIB = agentx_region(ctx, AGENTX_OID(VMMIB), 1)) == NULL)
+               fatal("agentx_region vmMIB");
+       
+       if ((vmIndex = agentx_index_integer_dynamic(vmMIB,
+           AGENTX_OID(VMINDEX))) == NULL)
+               fatal("agentx_index_integer_dynamic");
+        if ((agentx_object(vmMIB, AGENTX_OID(VMHVSOFTWARE), NULL, 0, 0,
+           vm_agentx_vmHvSoftware)) == NULL ||
+            (agentx_object(vmMIB, AGENTX_OID(VMHVVERSION), NULL, 0, 0,
+           vm_agentx_vmHvVersion)) == NULL ||
+            (agentx_object(vmMIB, AGENTX_OID(VMHVOBJECTID), NULL, 0, 0,
+           vm_agentx_vmHvObjectID)) == NULL ||
+            (vmNumber = agentx_object(vmMIB, AGENTX_OID(VMNUMBER), NULL, 0, 0,
+           vm_agentx_vminfo)) == NULL ||
+            (vmName = agentx_object(vmMIB, AGENTX_OID(VMNAME), &vmIndex, 1, 0,
+           vm_agentx_vminfo)) == NULL ||
+            (vmUUID = agentx_object(vmMIB, AGENTX_OID(VMUUID), &vmIndex, 1, 0,
+           vm_agentx_vminfo)) == NULL ||
+            (vmOSType = agentx_object(vmMIB, AGENTX_OID(VMOSTYPE), &vmIndex, 1,
+           0, vm_agentx_vminfo)) == NULL ||
+            (vmAdminState = agentx_object(vmMIB, AGENTX_OID(VMADMINSTATE),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmOperState = agentx_object(vmMIB, AGENTX_OID(VMOPERSTATE),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmAutoStart = agentx_object(vmMIB, AGENTX_OID(VMAUTOSTART),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmPersistent = agentx_object(vmMIB, AGENTX_OID(VMPERSISTENT),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmCurCpuNumber = agentx_object(vmMIB, AGENTX_OID(VMCURCPUNUMBER),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmMinCpuNumber = agentx_object(vmMIB, AGENTX_OID(VMMINCPUNUMBER),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmMaxCpuNumber = agentx_object(vmMIB, AGENTX_OID(VMMAXCPUNUMBER),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmMemUnit = agentx_object(vmMIB, AGENTX_OID(VMMEMUNIT),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmCurMem = agentx_object(vmMIB, AGENTX_OID(VMCURMEM),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmMinMem = agentx_object(vmMIB, AGENTX_OID(VMMINMEM),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
+            (vmMaxMem = agentx_object(vmMIB, AGENTX_OID(VMMAXMEM),
+           &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL)
+                fatal("agentx_object_ro");
+}
+
+static void
+vm_agentx_nofd(struct agentx *agentx, void *cookie, int close)
+{
+       struct conn *conn = cookie;
+
+       conn->agentx = agentx;
+       event_del(&conn->ev);
+       if (close)
+               free(conn);
+       else
+               vm_agentx_tryconnect(-1, 0, conn);
+}
+
+static void
+vm_agentx_tryconnect(int fd, short event, void *cookie)
+{
+       struct sockaddr_un sun;
+       struct timeval timeout = {3, 0};
+       struct conn *conn = cookie;
+
+       sun.sun_len = sizeof(sun);
+       sun.sun_family = AF_UNIX;
+       strlcpy(sun.sun_path, vmd_agentx->ax_path, sizeof(sun.sun_path));
+       if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+               log_warn("socket");
+               goto fail;
+       } else if (connect(fd, (struct sockaddr *)&sun, sun.sun_len) == -1) {
+               log_warn("connect");
+               close(fd);
+               goto fail;
+       }
+       agentx_connect(conn->agentx, fd);
+
+       event_set(&conn->ev, fd, EV_READ|EV_PERSIST, vm_agentx_read, conn);
+       event_add(&conn->ev, NULL);
+
+       return;
+fail:
+       evtimer_set(&conn->ev, vm_agentx_tryconnect, conn);
+       evtimer_add(&conn->ev, &timeout);
+}
+
+static void
+vm_agentx_read(int fd, short event, void *cookie)
+{
+       struct conn *conn = cookie;
+
+       agentx_read(conn->agentx);
+}
+
+static void
+vm_agentx_flush_pending(void)
+{
+       size_t i;
+
+       for (i = 0; i < vminfolen; i++)
+               agentx_varbind_notfound(vminfo[i]);
+       vminfolen = 0;
+}
+
+static int
+vm_agentx_sortvir(const void *c1, const void *c2)
+{
+       const struct vmop_info_result *v1 = c1, *v2 = c2;
+
+       return (v1->vir_info.vir_id < v2->vir_info.vir_id ? -1 :
+           v1->vir_info.vir_id > v2->vir_info.vir_id);
+}
+
+static int
+vm_agentx_adminstate(int mask)
+{
+       if (mask & VM_STATE_PAUSED)
+                return (3);
+        else if (mask & VM_STATE_RUNNING)
+                return (1);
+        else if (mask & VM_STATE_SHUTDOWN)
+                return (4);
+        /* Presence of absence of other flags */
+        else if (!mask || (mask & VM_STATE_DISABLED))
+                return (4);
+
+        return 4;
+}
+
+static int
+vm_agentx_operstate(int mask)
+{
+       if (mask & VM_STATE_PAUSED)
+                return (8);
+        else if (mask & VM_STATE_RUNNING)
+                return (4);
+        else if (mask & VM_STATE_SHUTDOWN)
+                return (11);
+        /* Presence of absence of other flags */
+        else if (!mask || (mask & VM_STATE_DISABLED))
+                return (11);
+
+        return (11);
+}
+
+static void
+vm_agentx_vmHvSoftware(struct agentx_varbind *vb)
+{
+       agentx_varbind_string(vb, "OpenBSD VMM");
+}
+
+static void
+vm_agentx_vmHvVersion(struct agentx_varbind *vb)
+{
+       int osversid[] = {CTL_KERN, KERN_OSRELEASE};
+       static char osvers[10] = "";
+       size_t osverslen;
+
+       if (osvers[0] == '\0') {
+               osverslen = sizeof(osvers);
+               if (sysctl(osversid, 2, osvers, &osverslen, NULL,
+                   0) == -1) {
+                       log_warn("Failed vmHvVersion sysctl");
+                       agentx_varbind_string(vb, "unknown");
+                       return;
+               }
+               if (osverslen >= sizeof(osvers))
+                       osverslen = sizeof(osvers) - 1;
+               osvers[osverslen] = '\0';
+       }
+       agentx_varbind_string(vb, osvers);
+}
+
+static void
+vm_agentx_vmHvObjectID(struct agentx_varbind *vb)
+{
+       agentx_varbind_oid(vb, AGENTX_OID(0, 0));
+}
+
+static void
+vm_agentx_vminfo(struct agentx_varbind *vb)
+{
+       extern struct vmd *env;
+       struct agentx_varbind **tvminfo;
+
+       if (vminfolen >= vminfosize) {
+               if ((tvminfo = reallocarray(vminfo, vminfosize + 10,
+                   sizeof(*vminfo))) == NULL) {
+                       log_warn("%s: Couldn't retrieve vm information",
+                           __func__);
+                       agentx_varbind_error(vb);
+                       return;
+               }
+               vminfo = tvminfo;
+               vminfosize += 10;
+       }
+
+       if (!vmcollecting) {
+               if (proc_compose_imsg(&(env->vmd_ps), PROC_PARENT, -1,
+                   IMSG_VMDOP_GET_INFO_VM_REQUEST, IMSG_AGENTX_PEERID,
+                   -1, NULL, 0) == -1) {
+                       log_warn("%s: Couldn't retrieve vm information",
+                           __func__);
+                       agentx_varbind_error(vb);
+                       return;
+               }
+               vmcollecting = 1;
+       }
+
+       vminfo[vminfolen++] = vb;
+}
index 74b4a3f..2f3ac1a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: vmd.c,v 1.131 2022/05/08 14:44:54 dv Exp $    */
+/*     $OpenBSD: vmd.c,v 1.132 2022/09/13 10:28:19 martijn Exp $       */
 
 /*
  * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
@@ -57,6 +57,7 @@ void   vmd_shutdown(void);
 int     vmd_control_run(void);
 int     vmd_dispatch_control(int, struct privsep_proc *, struct imsg *);
 int     vmd_dispatch_vmm(int, struct privsep_proc *, struct imsg *);
+int     vmd_dispatch_agentx(int, struct privsep_proc *, struct imsg *);
 int     vmd_dispatch_priv(int, struct privsep_proc *, struct imsg *);
 int     vmd_check_vmh(struct vm_dump_header *);
 
@@ -73,6 +74,7 @@ static struct privsep_proc procs[] = {
        { "priv",       PROC_PRIV,      vmd_dispatch_priv, priv },
        { "control",    PROC_CONTROL,   vmd_dispatch_control, control },
        { "vmm",        PROC_VMM,       vmd_dispatch_vmm, vmm, vmm_shutdown },
+       { "agentx",     PROC_AGENTX,    vmd_dispatch_agentx, vm_agentx, vm_agentx_shutdown, "/" }
 };
 
 enum privsep_procid privsep_process;
@@ -501,7 +503,9 @@ vmd_dispatch_vmm(int fd, struct privsep_proc *p, struct imsg *imsg)
                        vir.vir_uid = vm->vm_uid;
                        vir.vir_gid = vm->vm_params.vmc_owner.gid;
                }
-               if (proc_compose_imsg(ps, PROC_CONTROL, -1, imsg->hdr.type,
+               if (proc_compose_imsg(ps,
+                   imsg->hdr.peerid == IMSG_AGENTX_PEERID ?
+                   PROC_AGENTX : PROC_CONTROL, -1, imsg->hdr.type,
                    imsg->hdr.peerid, -1, &vir, sizeof(vir)) == -1) {
                        log_debug("%s: GET_INFO_VM failed for vm %d, removing",
                            __func__, vm->vm_vmid);
@@ -533,7 +537,9 @@ vmd_dispatch_vmm(int fd, struct privsep_proc *p, struct imsg *imsg)
                                log_debug("%s: vm: %d, vm_state: 0x%x",
                                    __func__, vm->vm_vmid, vm->vm_state);
                                vir.vir_state = vm->vm_state;
-                               if (proc_compose_imsg(ps, PROC_CONTROL, -1,
+                               if (proc_compose_imsg(ps,
+                                   imsg->hdr.peerid == IMSG_AGENTX_PEERID ?
+                                   PROC_AGENTX : PROC_CONTROL, -1,
                                    IMSG_VMDOP_GET_INFO_VM_DATA,
                                    imsg->hdr.peerid, -1, &vir,
                                    sizeof(vir)) == -1) {
@@ -545,7 +551,9 @@ vmd_dispatch_vmm(int fd, struct privsep_proc *p, struct imsg *imsg)
                        }
                }
                IMSG_SIZE_CHECK(imsg, &res);
-               proc_forward_imsg(ps, imsg, PROC_CONTROL, -1);
+               proc_forward_imsg(ps, imsg,
+                   imsg->hdr.peerid == IMSG_AGENTX_PEERID ?
+                   PROC_AGENTX : PROC_CONTROL, -1);
                break;
        default:
                return (-1);
@@ -554,6 +562,21 @@ vmd_dispatch_vmm(int fd, struct privsep_proc *p, struct imsg *imsg)
        return (0);
 }
 
+int
+vmd_dispatch_agentx(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+       struct privsep                  *ps = p->p_ps;
+
+       switch (imsg->hdr.type) {
+       case IMSG_VMDOP_GET_INFO_VM_REQUEST:
+               proc_forward_imsg(ps, imsg, PROC_VMM, -1);
+               return (0);
+       default:
+               break;
+       }
+       return (-1);
+}
+
 int
 vmd_dispatch_priv(int fd, struct privsep_proc *p, struct imsg *imsg)
 {
@@ -1236,24 +1259,24 @@ vm_claimid(const char *name, int uid, uint32_t *id)
 
        if (++env->vmd_nvm == 0) {
                log_warnx("too many vms");
-               return -1;
+               return (-1);
        }
        if ((n2i = calloc(1, sizeof(struct name2id))) == NULL) {
                log_warnx("could not alloc vm name");
-               return -1;
+               return (-1);
        }
        n2i->id = env->vmd_nvm;
        n2i->uid = uid;
        if (strlcpy(n2i->name, name, sizeof(n2i->name)) >= sizeof(n2i->name)) {
                log_warnx("vm name too long");
                free(n2i);
-               return -1;
+               return (-1);
        }
        TAILQ_INSERT_TAIL(env->vmd_known, n2i, entry);
 
 out:
        *id = n2i->id;
-       return 0;
+       return (0);
 }
 
 int
index 31bc642..9010ad6 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: vmd.h,v 1.109 2022/05/03 21:39:18 dv Exp $    */
+/*     $OpenBSD: vmd.h,v 1.110 2022/09/13 10:28:19 martijn Exp $       */
 
 /*
  * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
@@ -18,6 +18,7 @@
 
 #include <sys/types.h>
 #include <sys/queue.h>
+#include <sys/un.h>
 #include <sys/socket.h>
 
 #include <machine/vmmvar.h>
@@ -78,6 +79,8 @@
 #define VMD_CDROM_INVALID      1006
 #define VMD_PARENT_INVALID     1007
 
+#define IMSG_AGENTX_PEERID     (uint32_t)-2
+
 /* Image file signatures */
 #define VM_MAGIC_QCOW          "QFI\xfb"
 
@@ -330,6 +333,17 @@ struct address {
 };
 TAILQ_HEAD(addresslist, address);
 
+#define SUN_PATH_LEN           (sizeof(((struct sockaddr_un *)NULL)->sun_path))
+struct vmd_agentx {
+       int                      ax_enabled;
+       char                     ax_path[SUN_PATH_LEN];
+       /*
+        * SNMP-VIEW-BASED-ACM-MIB:vacmContextName
+        * Should probably be a define in agentx.h
+        */
+       char                     ax_context[32 + 1];
+};
+
 struct vmd_config {
        unsigned int             cfg_flags;
 #define VMD_CFG_INET6          0x01
@@ -340,6 +354,7 @@ struct vmd_config {
        int                      parallelism;
        struct address           cfg_localprefix;
        struct address           cfg_localprefix6;
+       struct vmd_agentx        cfg_agentx;
 };
 
 struct vmd {
@@ -481,6 +496,10 @@ int         config_getdisk(struct privsep *, struct imsg *);
 int     config_getif(struct privsep *, struct imsg *);
 int     config_getcdrom(struct privsep *, struct imsg *);
 
+/* vm_agentx.c */
+void vm_agentx(struct privsep *, struct privsep_proc *);
+void vm_agentx_shutdown(void);
+
 /* parse.y */
 int     parse_config(const char *);
 int     cmdline_symset(char *);