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