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