From f94ca20ec5b55214b353e4a4a5cc0290a5873a2c Mon Sep 17 00:00:00 2001 From: martijn Date: Tue, 13 Sep 2022 10:28:19 +0000 Subject: [PATCH] Add (partial) support for agentx in vmd. 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 | 7 +- usr.sbin/vmd/parse.y | 54 +++- usr.sbin/vmd/proc.h | 3 +- usr.sbin/vmd/vm.conf.5 | 17 +- usr.sbin/vmd/vm_agentx.c | 538 +++++++++++++++++++++++++++++++++++++++ usr.sbin/vmd/vmd.c | 39 ++- usr.sbin/vmd/vmd.h | 21 +- 7 files changed, 662 insertions(+), 17 deletions(-) create mode 100644 usr.sbin/vmd/vm_agentx.c diff --git a/usr.sbin/vmd/Makefile b/usr.sbin/vmd/Makefile index 0abaf00fdd1..42e94fd394b 100644 --- a/usr.sbin/vmd/Makefile +++ b/usr.sbin/vmd/Makefile @@ -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= diff --git a/usr.sbin/vmd/parse.y b/usr.sbin/vmd/parse.y index f8aebbe039f..3175e17de51 100644 --- a/usr.sbin/vmd/parse.y +++ b/usr.sbin/vmd/parse.y @@ -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 @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -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(); diff --git a/usr.sbin/vmd/proc.h b/usr.sbin/vmd/proc.h index dba9972dd41..123df4fe465 100644 --- a/usr.sbin/vmd/proc.h +++ b/usr.sbin/vmd/proc.h @@ -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 @@ -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, diff --git a/usr.sbin/vmd/vm.conf.5 b/usr.sbin/vmd/vm.conf.5 index d63a3890ed2..ca39b14c477 100644 --- a/usr.sbin/vmd/vm.conf.5 +++ b/usr.sbin/vmd/vm.conf.5 @@ -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 .\" Copyright (c) 2015 Reyk Floeter @@ -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 index 00000000000..e54133f325a --- /dev/null +++ b/usr.sbin/vmd/vm_agentx.c @@ -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 + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/usr.sbin/vmd/vmd.c b/usr.sbin/vmd/vmd.c index 74b4a3ffe8e..2f3ac1a76f2 100644 --- a/usr.sbin/vmd/vmd.c +++ b/usr.sbin/vmd/vmd.c @@ -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 @@ -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 diff --git a/usr.sbin/vmd/vmd.h b/usr.sbin/vmd/vmd.h index 31bc642afdb..9010ad6eb9f 100644 --- a/usr.sbin/vmd/vmd.h +++ b/usr.sbin/vmd/vmd.h @@ -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 @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -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 *); -- 2.20.1