xref: /openbsd-src/usr.sbin/vmd/vm_agentx.c (revision 65bbee46cad7861cd5a570f338df9e976422e3ab)
1 /*	$OpenBSD: vm_agentx.c,v 1.3 2024/09/26 01:45:13 jsg Exp $ */
2 
3 /*
4  * Copyright (c) 2022 Martijn van Duren <martijn@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/sysctl.h>
21 #include <sys/un.h>
22 
23 #include <agentx.h>
24 #include <grp.h>
25 #include <pwd.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "proc.h"
31 #include "vmd.h"
32 
33 struct conn {
34 	struct event ev;
35 	struct agentx *agentx;
36 };
37 
38 void vm_agentx_run(struct privsep *, struct privsep_proc *, void *);
39 int vm_agentx_dispatch_parent(int, struct privsep_proc *, struct imsg *);
40 void vm_agentx_configure(struct vmd_agentx *);
41 static void vm_agentx_nofd(struct agentx *, void *, int);
42 static void vm_agentx_tryconnect(int, short, void *);
43 static void vm_agentx_read(int, short, void *);
44 static void vm_agentx_flush_pending(void);
45 static int vm_agentx_sortvir(const void *, const void *);
46 static int vm_agentx_adminstate(int);
47 static int vm_agentx_operstate(int);
48 static void vm_agentx_vmHvSoftware(struct agentx_varbind *);
49 static void vm_agentx_vmHvVersion(struct agentx_varbind *);
50 static void vm_agentx_vmHvObjectID(struct agentx_varbind *);
51 static void vm_agentx_vminfo(struct agentx_varbind *);
52 
53 static struct agentx_index *vmIndex;
54 static struct agentx_object *vmNumber, *vmName, *vmUUID, *vmOSType;
55 static struct agentx_object *vmAdminState, *vmOperState, *vmAutoStart;
56 static struct agentx_object *vmPersistent, *vmCurCpuNumber, *vmMinCpuNumber;
57 static struct agentx_object *vmMaxCpuNumber, *vmMemUnit, *vmCurMem, *vmMinMem;
58 static struct agentx_object *vmMaxMem;
59 
60 static struct vmd_agentx *vmd_agentx;
61 
62 static struct agentx_varbind **vminfo = NULL;
63 static size_t vminfolen = 0;
64 static size_t vminfosize = 0;
65 static int vmcollecting = 0;
66 
67 #define VMMIB AGENTX_MIB2, 236
68 #define VMOBJECTS VMMIB, 1
69 #define VMHVSOFTWARE VMOBJECTS, 1, 1
70 #define VMHVVERSION VMOBJECTS, 1, 2
71 #define VMHVOBJECTID VMOBJECTS, 1, 3
72 #define VMNUMBER VMOBJECTS, 2
73 #define VMENTRY VMOBJECTS, 4, 1
74 #define VMINDEX VMENTRY, 1
75 #define VMNAME VMENTRY, 2
76 #define VMUUID VMENTRY, 3
77 #define VMOSTYPE VMENTRY, 4
78 #define VMADMINSTATE VMENTRY, 5
79 #define VMOPERSTATE VMENTRY, 6
80 #define VMAUTOSTART VMENTRY, 7
81 #define VMPERSISTENT VMENTRY, 8
82 #define VMCURCPUNUMBER VMENTRY, 9
83 #define VMMINCPUNUMBER VMENTRY, 10
84 #define VMMAXCPUNUMBER VMENTRY, 11
85 #define VMMEMUNIT VMENTRY, 12
86 #define VMCURMEM VMENTRY, 13
87 #define VMMINMEM VMENTRY, 14
88 #define VMMAXMEM VMENTRY, 15
89 
90 #define AGENTX_GROUP "_agentx"
91 #define MEM_SCALE (1024 * 1024)
92 
93 static struct privsep_proc procs[] = {
94 	{ "parent",	PROC_PARENT,	vm_agentx_dispatch_parent  },
95 };
96 
97 void
98 vm_agentx(struct privsep *ps, struct privsep_proc *p)
99 {
100 	struct group *grp;
101 
102 	/*
103 	 * Make sure we can connect to /var/agentx/master with the correct
104 	 * group permissions.
105 	 */
106 	if ((grp = getgrnam(AGENTX_GROUP)) == NULL)
107 		fatal("failed to get group: %s", AGENTX_GROUP);
108 	ps->ps_pw->pw_gid = grp->gr_gid;
109 
110 	proc_run(ps, p, procs, nitems(procs), vm_agentx_run, NULL);
111 }
112 
113 void
114 vm_agentx_shutdown(void)
115 {
116 }
117 
118 void
119 vm_agentx_run(struct privsep *ps, struct privsep_proc *p, void *arg)
120 {
121 	/*
122 	 * pledge in agentx process
123 	 * stdio - for malloc and basic I/O including events.
124 	 * recvfd - for the proc fd exchange.
125 	 * unix - for access to the agentx master socket.
126 	 */
127 	if (pledge("stdio recvfd unix", NULL) == -1)
128 		fatal("pledge");
129 }
130 
131 int
132 vm_agentx_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
133 {
134 	static struct vmop_info_result *vir = NULL;
135 	static struct vmop_info_result *mvir = NULL;
136 	struct vmd *env = p->p_ps->ps_env;
137 	struct vmop_info_result *tvir;
138 	struct agentx_object *reqObject;
139 	static size_t nvir = 0;
140 	static size_t virlen = 0;
141 	static int error = 0;
142 	size_t i, j, index;
143 	enum agentx_request_type rtype;
144 
145 	switch (imsg->hdr.type) {
146 	case IMSG_VMDOP_GET_INFO_VM_DATA:
147 		if (error)
148 			break;
149 		if (nvir + 1 > virlen) {
150 			tvir = reallocarray(vir, virlen + 10, sizeof(*vir));
151 			if (tvir == NULL) {
152 				log_warn("%s: Couldn't dispatch vm information",
153 				    __func__);
154 				error = 1;
155 				break;
156 			}
157 			virlen += 10;
158 			vir = tvir;
159 		}
160                 memcpy(&(vir[nvir++]), imsg->data, sizeof(vir[nvir]));
161 		break;
162 	case IMSG_VMDOP_GET_INFO_VM_END_DATA:
163 		vmcollecting = 0;
164 		if (error) {
165 			for (i = 0; i < vminfolen; i++)
166 				agentx_varbind_error(vminfo[i]);
167 			vminfolen = 0;
168 			error = 0;
169 			nvir = 0;
170 			return (0);
171 		}
172 
173 		qsort(vir, nvir, sizeof(*vir), vm_agentx_sortvir);
174 		for (i = 0; i < vminfolen; i++) {
175 			reqObject = agentx_varbind_get_object(vminfo[i]);
176 			if (reqObject == vmNumber) {
177 				agentx_varbind_integer(vminfo[i],
178 				    (int32_t)nvir);
179 				continue;
180 			}
181 			index = agentx_varbind_get_index_integer(vminfo[i],
182 			    vmIndex);
183 			rtype = agentx_varbind_request(vminfo[i]);
184 			for (j = 0; j < nvir; j++) {
185 				if (vir[j].vir_info.vir_id < index)
186 					continue;
187 				if (vir[j].vir_info.vir_id == index &&
188 				    (rtype == AGENTX_REQUEST_TYPE_GET ||
189 				    rtype ==
190 				    AGENTX_REQUEST_TYPE_GETNEXTINCLUSIVE))
191 					break;
192 				if (vir[j].vir_info.vir_id > index &&
193 				    (rtype == AGENTX_REQUEST_TYPE_GETNEXT ||
194 				    rtype ==
195 				    AGENTX_REQUEST_TYPE_GETNEXTINCLUSIVE))
196 					break;
197 			}
198 			if (j == nvir) {
199 				agentx_varbind_notfound(vminfo[i]);
200 				continue;
201 			}
202 			mvir = &(vir[j]);
203 			agentx_varbind_set_index_integer(vminfo[i], vmIndex,
204 			    mvir->vir_info.vir_id);
205 			if (reqObject == vmName)
206 				agentx_varbind_string(vminfo[i],
207 				    mvir->vir_info.vir_name);
208 			else if (reqObject == vmUUID)
209 				agentx_varbind_string(vminfo[i], "");
210 			else if (reqObject == vmOSType)
211 				agentx_varbind_string(vminfo[i], "");
212 			else if (reqObject == vmAdminState)
213 				agentx_varbind_integer(vminfo[i],
214 				    vm_agentx_adminstate(mvir->vir_state));
215 			else if (reqObject == vmOperState)
216 				agentx_varbind_integer(vminfo[i],
217 				    vm_agentx_operstate(mvir->vir_state));
218 			else if (reqObject == vmAutoStart)
219 				agentx_varbind_integer(vminfo[i],
220 				    mvir->vir_state & VM_STATE_DISABLED ?
221 				    3 : 2);
222 /* XXX We can dynamically create vm's but I don't know how to differentiate */
223 			else if (reqObject == vmPersistent)
224 				agentx_varbind_integer(vminfo[i], 1);
225 /* We currently only support a single CPU */
226 			else if (reqObject == vmCurCpuNumber ||
227 			    reqObject == vmMinCpuNumber ||
228 			    reqObject == vmMaxCpuNumber)
229 				agentx_varbind_integer(vminfo[i],
230 				    mvir->vir_info.vir_ncpus);
231 			else if (reqObject == vmMemUnit)
232 				agentx_varbind_integer(vminfo[i], MEM_SCALE);
233 			else if (reqObject == vmCurMem)
234 				agentx_varbind_integer(vminfo[i],
235 				    mvir->vir_info.vir_used_size / MEM_SCALE);
236 			else if (reqObject == vmMinMem)
237 				agentx_varbind_integer(vminfo[i], -1);
238 			else if (reqObject == vmMaxMem)
239 				agentx_varbind_integer(vminfo[i],
240 				    mvir->vir_info.vir_memory_size / MEM_SCALE);
241 /* We probably had a reload */
242 			else
243 				agentx_varbind_notfound(vminfo[i]);
244 		}
245 		vminfolen = 0;
246 		nvir = 0;
247 		break;
248 	case IMSG_VMDOP_CONFIG:
249 		config_getconfig(env, imsg);
250 		vm_agentx_configure(&env->vmd_cfg.cfg_agentx);
251 		break;
252 	default:
253 		return (-1);
254 	}
255 	return (0);
256 }
257 
258 void
259 vm_agentx_configure(struct vmd_agentx *env)
260 {
261 	static char curpath[sizeof(env->ax_path)];
262 	static char curcontext[sizeof(env->ax_context)];
263 	static struct conn *conn;
264 	static struct agentx_session *sess;
265 	static struct agentx_context *ctx;
266 	struct agentx_region *vmMIB;
267 	char *context = env->ax_context[0] == '\0' ? NULL : env->ax_context;
268 	int changed = 0;
269 
270 	vmd_agentx = env;
271 
272 	agentx_log_fatal = fatalx;
273 	agentx_log_warn = log_warnx;
274 	agentx_log_info = log_info;
275 	agentx_log_debug = log_debug;
276 
277 	if (!vmd_agentx->ax_enabled) {
278 		if (conn != NULL) {
279 			agentx_free(conn->agentx);
280 			conn = NULL;
281 			sess = NULL;
282 			ctx = NULL;
283 			vm_agentx_flush_pending();
284 		}
285 		return;
286 	}
287 
288 	if (strcmp(curpath, vmd_agentx->ax_path) != 0 || conn == NULL) {
289 		if (conn != NULL) {
290 			agentx_free(conn->agentx);
291 			conn = NULL;
292 			sess = NULL;
293 			ctx = NULL;
294 			vm_agentx_flush_pending();
295 		}
296 
297 		if ((conn = malloc(sizeof(*conn))) == NULL)
298 			fatal(NULL);
299 		/* Set to something so we can safely event_del */
300 		evtimer_set(&conn->ev, vm_agentx_tryconnect, conn);
301 		/* result assigned in vm_agentx_nofd */
302 		if (agentx(vm_agentx_nofd, conn) == NULL)
303 			fatal("Can't setup agentx");
304 		sess = agentx_session(conn->agentx, NULL, 0, "vmd", 0);
305 		if (sess == NULL)
306 			fatal("Can't setup agentx session");
307 		(void) strlcpy(curpath, vmd_agentx->ax_path, sizeof(curpath));
308 		changed = 1;
309 	}
310 
311 	if (strcmp(curcontext, vmd_agentx->ax_context) != 0 || changed) {
312 		if (!changed) {
313 			agentx_context_free(ctx);
314 			vm_agentx_flush_pending();
315 		}
316 		if ((ctx = agentx_context(sess, context)) == NULL)
317 			fatal("Can't setup agentx context");
318 		strlcpy(curcontext, vmd_agentx->ax_context, sizeof(curcontext));
319 		changed = 1;
320 	}
321 
322 	if (!changed)
323 		return;
324 
325 	if ((vmMIB = agentx_region(ctx, AGENTX_OID(VMMIB), 1)) == NULL)
326 		fatal("agentx_region vmMIB");
327 
328 	if ((vmIndex = agentx_index_integer_dynamic(vmMIB,
329 	    AGENTX_OID(VMINDEX))) == NULL)
330 		fatal("agentx_index_integer_dynamic");
331         if ((agentx_object(vmMIB, AGENTX_OID(VMHVSOFTWARE), NULL, 0, 0,
332 	    vm_agentx_vmHvSoftware)) == NULL ||
333             (agentx_object(vmMIB, AGENTX_OID(VMHVVERSION), NULL, 0, 0,
334 	    vm_agentx_vmHvVersion)) == NULL ||
335             (agentx_object(vmMIB, AGENTX_OID(VMHVOBJECTID), NULL, 0, 0,
336 	    vm_agentx_vmHvObjectID)) == NULL ||
337             (vmNumber = agentx_object(vmMIB, AGENTX_OID(VMNUMBER), NULL, 0, 0,
338 	    vm_agentx_vminfo)) == NULL ||
339             (vmName = agentx_object(vmMIB, AGENTX_OID(VMNAME), &vmIndex, 1, 0,
340 	    vm_agentx_vminfo)) == NULL ||
341             (vmUUID = agentx_object(vmMIB, AGENTX_OID(VMUUID), &vmIndex, 1, 0,
342 	    vm_agentx_vminfo)) == NULL ||
343             (vmOSType = agentx_object(vmMIB, AGENTX_OID(VMOSTYPE), &vmIndex, 1,
344 	    0, vm_agentx_vminfo)) == NULL ||
345             (vmAdminState = agentx_object(vmMIB, AGENTX_OID(VMADMINSTATE),
346 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
347             (vmOperState = agentx_object(vmMIB, AGENTX_OID(VMOPERSTATE),
348 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
349             (vmAutoStart = agentx_object(vmMIB, AGENTX_OID(VMAUTOSTART),
350 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
351             (vmPersistent = agentx_object(vmMIB, AGENTX_OID(VMPERSISTENT),
352 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
353             (vmCurCpuNumber = agentx_object(vmMIB, AGENTX_OID(VMCURCPUNUMBER),
354 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
355             (vmMinCpuNumber = agentx_object(vmMIB, AGENTX_OID(VMMINCPUNUMBER),
356 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
357             (vmMaxCpuNumber = agentx_object(vmMIB, AGENTX_OID(VMMAXCPUNUMBER),
358 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
359             (vmMemUnit = agentx_object(vmMIB, AGENTX_OID(VMMEMUNIT),
360 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
361             (vmCurMem = agentx_object(vmMIB, AGENTX_OID(VMCURMEM),
362 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
363             (vmMinMem = agentx_object(vmMIB, AGENTX_OID(VMMINMEM),
364 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL ||
365             (vmMaxMem = agentx_object(vmMIB, AGENTX_OID(VMMAXMEM),
366 	    &vmIndex, 1, 0, vm_agentx_vminfo)) == NULL)
367                 fatal("agentx_object_ro");
368 }
369 
370 static void
371 vm_agentx_nofd(struct agentx *agentx, void *cookie, int close)
372 {
373 	struct conn *conn = cookie;
374 
375 	conn->agentx = agentx;
376 	event_del(&conn->ev);
377 	if (close)
378 		free(conn);
379 	else
380 		vm_agentx_tryconnect(-1, 0, conn);
381 }
382 
383 static void
384 vm_agentx_tryconnect(int fd, short event, void *cookie)
385 {
386 	struct sockaddr_un sun;
387 	struct timeval timeout = {3, 0};
388 	struct conn *conn = cookie;
389 
390 	sun.sun_len = sizeof(sun);
391 	sun.sun_family = AF_UNIX;
392 	strlcpy(sun.sun_path, vmd_agentx->ax_path, sizeof(sun.sun_path));
393 	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
394 		log_warn("socket");
395 		goto fail;
396 	} else if (connect(fd, (struct sockaddr *)&sun, sun.sun_len) == -1) {
397 		log_warn("connect");
398 		close(fd);
399 		goto fail;
400 	}
401 	agentx_connect(conn->agentx, fd);
402 
403 	event_set(&conn->ev, fd, EV_READ|EV_PERSIST, vm_agentx_read, conn);
404 	event_add(&conn->ev, NULL);
405 
406 	return;
407 fail:
408 	evtimer_set(&conn->ev, vm_agentx_tryconnect, conn);
409 	evtimer_add(&conn->ev, &timeout);
410 }
411 
412 static void
413 vm_agentx_read(int fd, short event, void *cookie)
414 {
415 	struct conn *conn = cookie;
416 
417 	agentx_read(conn->agentx);
418 }
419 
420 static void
421 vm_agentx_flush_pending(void)
422 {
423 	size_t i;
424 
425 	for (i = 0; i < vminfolen; i++)
426 		agentx_varbind_notfound(vminfo[i]);
427 	vminfolen = 0;
428 }
429 
430 static int
431 vm_agentx_sortvir(const void *c1, const void *c2)
432 {
433 	const struct vmop_info_result *v1 = c1, *v2 = c2;
434 
435 	return (v1->vir_info.vir_id < v2->vir_info.vir_id ? -1 :
436 	    v1->vir_info.vir_id > v2->vir_info.vir_id);
437 }
438 
439 static int
440 vm_agentx_adminstate(int mask)
441 {
442 	if (mask & VM_STATE_PAUSED)
443                 return (3);
444         else if (mask & VM_STATE_RUNNING)
445                 return (1);
446         else if (mask & VM_STATE_SHUTDOWN)
447                 return (4);
448         /* Presence of absence of other flags */
449         else if (!mask || (mask & VM_STATE_DISABLED))
450                 return (4);
451 
452         return 4;
453 }
454 
455 static int
456 vm_agentx_operstate(int mask)
457 {
458 	if (mask & VM_STATE_PAUSED)
459                 return (8);
460         else if (mask & VM_STATE_RUNNING)
461                 return (4);
462         else if (mask & VM_STATE_SHUTDOWN)
463                 return (11);
464         /* Presence of absence of other flags */
465         else if (!mask || (mask & VM_STATE_DISABLED))
466                 return (11);
467 
468         return (11);
469 }
470 
471 static void
472 vm_agentx_vmHvSoftware(struct agentx_varbind *vb)
473 {
474 	agentx_varbind_string(vb, "OpenBSD VMM");
475 }
476 
477 static void
478 vm_agentx_vmHvVersion(struct agentx_varbind *vb)
479 {
480 	int osversid[] = {CTL_KERN, KERN_OSRELEASE};
481 	static char osvers[10] = "";
482 	size_t osverslen;
483 
484 	if (osvers[0] == '\0') {
485 		osverslen = sizeof(osvers);
486 		if (sysctl(osversid, 2, osvers, &osverslen, NULL,
487 		    0) == -1) {
488 			log_warn("Failed vmHvVersion sysctl");
489 			agentx_varbind_string(vb, "unknown");
490 			return;
491 		}
492 		if (osverslen >= sizeof(osvers))
493 			osverslen = sizeof(osvers) - 1;
494 		osvers[osverslen] = '\0';
495 	}
496 	agentx_varbind_string(vb, osvers);
497 }
498 
499 static void
500 vm_agentx_vmHvObjectID(struct agentx_varbind *vb)
501 {
502 	agentx_varbind_oid(vb, AGENTX_OID(0, 0));
503 }
504 
505 static void
506 vm_agentx_vminfo(struct agentx_varbind *vb)
507 {
508 	extern struct vmd *env;
509 	struct agentx_varbind **tvminfo;
510 
511 	if (vminfolen >= vminfosize) {
512 		if ((tvminfo = reallocarray(vminfo, vminfosize + 10,
513 		    sizeof(*vminfo))) == NULL) {
514 			log_warn("%s: Couldn't retrieve vm information",
515 			    __func__);
516 			agentx_varbind_error(vb);
517 			return;
518 		}
519 		vminfo = tvminfo;
520 		vminfosize += 10;
521 	}
522 
523 	if (!vmcollecting) {
524 		if (proc_compose_imsg(&(env->vmd_ps), PROC_PARENT, -1,
525 		    IMSG_VMDOP_GET_INFO_VM_REQUEST, IMSG_AGENTX_PEERID,
526 		    -1, NULL, 0) == -1) {
527 			log_warn("%s: Couldn't retrieve vm information",
528 			    __func__);
529 			agentx_varbind_error(vb);
530 			return;
531 		}
532 		vmcollecting = 1;
533 	}
534 
535 	vminfo[vminfolen++] = vb;
536 }
537