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
vm_agentx(struct privsep * ps,struct privsep_proc * p)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
vm_agentx_shutdown(void)114 vm_agentx_shutdown(void)
115 {
116 }
117
118 void
vm_agentx_run(struct privsep * ps,struct privsep_proc * p,void * arg)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
vm_agentx_dispatch_parent(int fd,struct privsep_proc * p,struct imsg * imsg)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
vm_agentx_configure(struct vmd_agentx * env)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
vm_agentx_nofd(struct agentx * agentx,void * cookie,int close)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
vm_agentx_tryconnect(int fd,short event,void * cookie)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
vm_agentx_read(int fd,short event,void * cookie)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
vm_agentx_flush_pending(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
vm_agentx_sortvir(const void * c1,const void * c2)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
vm_agentx_adminstate(int mask)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
vm_agentx_operstate(int mask)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
vm_agentx_vmHvSoftware(struct agentx_varbind * vb)472 vm_agentx_vmHvSoftware(struct agentx_varbind *vb)
473 {
474 agentx_varbind_string(vb, "OpenBSD VMM");
475 }
476
477 static void
vm_agentx_vmHvVersion(struct agentx_varbind * vb)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
vm_agentx_vmHvObjectID(struct agentx_varbind * vb)500 vm_agentx_vmHvObjectID(struct agentx_varbind *vb)
501 {
502 agentx_varbind_oid(vb, AGENTX_OID(0, 0));
503 }
504
505 static void
vm_agentx_vminfo(struct agentx_varbind * vb)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