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