diff options
author | David Gwynne <dlg@cvs.openbsd.org> | 2010-10-26 00:57:22 +0000 |
---|---|---|
committer | David Gwynne <dlg@cvs.openbsd.org> | 2010-10-26 00:57:22 +0000 |
commit | 8beb208c67ddf49b42bd8bf76422302a8ae04cf9 (patch) | |
tree | e1bb6888457285b261c775167422f73521ba5b94 /sys/dev | |
parent | 180a207e8cf8bd0826c6198ed5a136f76b78daf6 (diff) |
big update to vmware tools functionality from jonathan matthew.
vmt(4) will now:
- initiate shutdown by signalling init with SIGUSR2 when requested by
the host.
- initiate a reboot by signalling init with SIGINT when requested by
the host.
- report the guests hostname to the host
- report the guests first non-loopback IP address to the host
- report the guests uptime to the host
- update the guests timedelta sensor using the 64bit rpc
vmt(4) now does the majority of what people actually need.
this has been tested on vmware server 2.0.2 with linux as the host,
vmware esx 4.0, and vmware esx 4.1
testing by jonathan matthew and myself. previous versions of the changes
were also tested by johan allard and srebrenko sehic.
code reviewed by and ok matthew@
thanks moch and johan :)
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/vmt.c | 596 |
1 files changed, 497 insertions, 99 deletions
diff --git a/sys/dev/vmt.c b/sys/dev/vmt.c index 18d6e1da560..2310ab3a20b 100644 --- a/sys/dev/vmt.c +++ b/sys/dev/vmt.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vmt.c,v 1.7 2009/12/28 14:25:34 dlg Exp $ */ +/* $OpenBSD: vmt.c,v 1.8 2010/10/26 00:57:21 dlg Exp $ */ /* * Copyright (c) 2007 David Crawshaw <david@zentus.com> @@ -34,6 +34,12 @@ #include <sys/types.h> #include <sys/malloc.h> #include <sys/timeout.h> +#include <sys/signalvar.h> +#include <sys/syslog.h> +#include <sys/proc.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> /* "The" magic number, always occupies the EAX register. */ #define VM_MAGIC 0x564D5868 @@ -55,15 +61,16 @@ #define VM_VERSION_UNMANAGED 0x7fffffff #define VM_CMD_GET_DEVINFO 0x0b #define VM_CMD_DEV_ADDREMOVE 0x0c -#define VM_CMD_GET_GUI_OPTIONS 0x0d +#define VM_CMD_GET_GUI_OPTIONS 0x0d #define VM_CMD_SET_GUI_OPTIONS 0x0e #define VM_CMD_GET_SCREEN_SIZE 0x0f #define VM_CMD_GET_HWVER 0x11 #define VM_CMD_POPUP_OSNOTFOUND 0x12 #define VM_CMD_GET_BIOS_UUID 0x13 #define VM_CMD_GET_MEM_SIZE 0x14 -#define VM_CMD_GET_TIME 0x17 +/*#define VM_CMD_GET_TIME 0x17 */ /* deprecated */ #define VM_CMD_RPC 0x1e +#define VM_CMD_GET_TIME_FULL 0x2e /* RPC sub-commands, passed on ECX.HIGH. */ #define VM_RPC_OPEN 0x00 @@ -75,9 +82,43 @@ #define VM_RPC_CLOSE 0x06 /* RPC magic numbers, passed on EBX. */ -#define VM_RPC_OPEN_RPCI 0x49435052UL /* with VM_RPC_OPEN. */ -#define VM_RPC_OPEN_RPCI_ENH 0xC9435052UL /* with VM_RPC_OPEN, enhanced. */ -#define VM_RPC_ENH_DATA 0x00010000UL /* with enhanced RPC data calls. */ +#define VM_RPC_OPEN_RPCI 0x49435052UL /* with VM_RPC_OPEN. */ +#define VM_RPC_OPEN_TCLO 0x4F4C4354UL /* with VP_RPC_OPEN. */ +#define VM_RPC_ENH_DATA 0x00010000UL /* with enhanced RPC data calls. */ + +#define VM_RPC_FLAG_COOKIE 0x80000000UL + +/* RPC reply flags */ +#define VM_RPC_REPLY_SUCCESS 0x0001 +#define VM_RPC_REPLY_DORECV 0x0002 /* incoming message available */ +#define VM_RPC_REPLY_CLOSED 0x0004 /* RPC channel is closed */ +#define VM_RPC_REPLY_UNSENT 0x0008 /* incoming message was removed? */ +#define VM_RPC_REPLY_CHECKPOINT 0x0010 /* checkpoint occurred -> retry */ +#define VM_RPC_REPLY_POWEROFF 0x0020 /* underlying device is powering off */ +#define VM_RPC_REPLY_TIMEOUT 0x0040 +#define VM_RPC_REPLY_HB 0x0080 /* high-bandwidth tx/rx available */ + +/* VM state change IDs */ +#define VM_STATE_CHANGE_HALT 1 +#define VM_STATE_CHANGE_REBOOT 2 +#define VM_STATE_CHANGE_POWERON 3 + +/* VM guest info keys */ +#define VM_GUEST_INFO_DNS_NAME 1 +#define VM_GUEST_INFO_IP_ADDRESS 2 +#define VM_GUEST_INFO_DISK_FREE_SPACE 3 +#define VM_GUEST_INFO_BUILD_NUMBER 4 +#define VM_GUEST_INFO_OS_NAME_FULL 5 +#define VM_GUEST_INFO_OS_NAME 6 +#define VM_GUEST_INFO_UPTIME 7 +#define VM_GUEST_INFO_MEMORY 8 +#define VM_GUEST_INFO_IP_ADDRESS_V2 9 + +/* RPC responses */ +#define VM_RPC_REPLY_OK "OK " +#define VM_RPC_RESET_REPLY "OK ATR toolbox" +#define VM_RPC_REPLY_ERROR "ERROR Unknown command" +#define VM_RPC_REPLY_ERROR_IP_ADDR "ERROR Unable to find guest IP address" /* A register. */ union vm_reg { @@ -119,11 +160,15 @@ void vmt_attach(struct device *, struct device *, void *); struct vmt_softc { struct device sc_dev; - struct vm_rpc sc_rpc; + struct vm_rpc sc_tclo_rpc; char *sc_rpc_buf; + int sc_rpc_error; + int sc_tclo_ping; + int sc_set_guest_os; #define VMT_RPC_BUFLEN 256 struct timeout sc_tick; + struct timeout sc_tclo_tick; struct ksensordev sc_sensordev; struct ksensor sc_sensor; @@ -136,7 +181,7 @@ struct cfattach vmt_ca = { vmt_match, vmt_attach }; - + struct cfdriver vmt_cd = { NULL, "vmt", @@ -148,13 +193,27 @@ void vm_ins(struct vm_backdoor *); void vm_outs(struct vm_backdoor *); /* Functions for communicating with the VM Host. */ -int vm_rpc_open(struct vm_rpc *); +int vm_rpc_open(struct vm_rpc *, uint32_t); int vm_rpc_close(struct vm_rpc *); int vm_rpc_send(const struct vm_rpc *, const uint8_t *, uint32_t); +int vm_rpc_send_str(const struct vm_rpc *, const uint8_t *); int vm_rpc_get_length(const struct vm_rpc *, uint32_t *, uint16_t *); int vm_rpc_get_data(const struct vm_rpc *, char *, uint32_t, uint16_t); +int vm_rpc_send_rpci_tx_buf(struct vmt_softc *, const uint8_t *, uint32_t); +int vm_rpc_send_rpci_tx(struct vmt_softc *, const char *, ...) + __attribute__((__format__(__kprintf__,2,3))); +int vm_rpci_response_successful(struct vmt_softc *); + +void vmt_tclo_state_change_success(struct vmt_softc *, int, char); +void vmt_do_reboot(struct vmt_softc *); +void vmt_do_shutdown(struct vmt_softc *); + +void vmt_update_guest_info(struct vmt_softc *); +void vmt_update_guest_uptime(struct vmt_softc *); void vmt_tick(void *); +void vmt_tclo_tick(void *); +void vmt_shutdown_hook(void *); extern char hostname[MAXHOSTNAMELEN]; @@ -192,9 +251,6 @@ void vmt_attach(struct device *parent, struct device *self, void *aux) { struct vmt_softc *sc = (struct vmt_softc *)self; - size_t len; - u_int32_t rlen; - u_int16_t ack; sc->sc_rpc_buf = malloc(VMT_RPC_BUFLEN, M_DEVBUF, M_NOWAIT); if (sc->sc_rpc_buf == NULL) { @@ -202,45 +258,18 @@ vmt_attach(struct device *parent, struct device *self, void *aux) return; } - if (vm_rpc_open(&sc->sc_rpc) != 0) { - printf(": failed to open backdoor RPC channel\n"); + if (vm_rpc_open(&sc->sc_tclo_rpc, VM_RPC_OPEN_TCLO) != 0) { + printf(": failed to open backdoor RPC channel (TCLO protocol)\n"); goto free; } - len = snprintf(sc->sc_rpc_buf, VMT_RPC_BUFLEN, "tools.set.version %u ", - VM_VERSION_UNMANAGED); -#ifdef DIAGNOSTIC - if (len > VMT_RPC_BUFLEN) - panic("vmt rpc buffer is too small"); -#endif - - if (vm_rpc_send(&sc->sc_rpc, sc->sc_rpc_buf, len) != 0) { - printf("%s: failed to send version\n", DEVNAME(sc)); - return; - } - - if (vm_rpc_get_length(&sc->sc_rpc, &rlen, &ack) != 0) { - printf("%s: failed to get length of version reply\n", - DEVNAME(sc)); - return; - } - - if (rlen > VMT_RPC_BUFLEN) { - printf("%s: reply is too large for version buffer\n", - DEVNAME(sc)); - return; - } - - bzero(sc->sc_rpc_buf, VMT_RPC_BUFLEN); - if (vm_rpc_get_data(&sc->sc_rpc, sc->sc_rpc_buf, rlen, ack) != 0) { - printf("%s: failed to get version reply\n", DEVNAME(sc)); - return; + /* don't know if this is important at all yet */ + if (vm_rpc_send_rpci_tx(sc, "tools.capability.hgfs_server toolbox 1") != 0) { + printf(": failed to set HGFS server capability\n"); + goto free; } - if (sc->sc_rpc_buf[0] != '1' && sc->sc_rpc_buf[1] != ' ') { - printf("%s: setting version failed\n", DEVNAME(sc)); - return; - } + shutdownhook_establish(vmt_shutdown_hook, sc); strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, sizeof(sc->sc_sensordev.xname)); @@ -254,6 +283,10 @@ vmt_attach(struct device *parent, struct device *self, void *aux) timeout_set(&sc->sc_tick, vmt_tick, sc); timeout_add_sec(&sc->sc_tick, 1); + timeout_set(&sc->sc_tclo_tick, vmt_tclo_tick, sc); + timeout_add_sec(&sc->sc_tclo_tick, 1); + sc->sc_tclo_ping = 1; + printf("\n"); return; @@ -262,6 +295,56 @@ free: } void +vmt_update_guest_uptime(struct vmt_softc *sc) +{ + /* host wants uptime in hundredths of a second */ + if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %lu00", + VM_GUEST_INFO_UPTIME, time_uptime) != 0) { + printf("%s: unable to set guest uptime", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } +} + +void +vmt_update_guest_info(struct vmt_softc *sc) +{ + if (strncmp(sc->sc_hostname, hostname, sizeof(sc->sc_hostname)) != 0) { + strlcpy(sc->sc_hostname, hostname, sizeof(sc->sc_hostname)); + + if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %s", + VM_GUEST_INFO_DNS_NAME, sc->sc_hostname) != 0) { + printf("%s: unable to set hostname", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + } + + /* + * we're supposed to pass the full network address information back here, + * but that involves xdr (sunrpc) data encoding, which seems a bit unreasonable. + */ + + if (sc->sc_set_guest_os == 0) { + if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %s %s %s", + VM_GUEST_INFO_OS_NAME_FULL, ostype, osrelease, osversion) != 0) { + printf("%s: unable to set full guest OS", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + + /* + * host doesn't like it if we send an OS name it doesn't recognise, + * so use the closest match, which happens to be FreeBSD. + */ + if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %s", + VM_GUEST_INFO_OS_NAME, "FreeBSD") != 0) { + printf("%s: unable to set guest OS", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + + sc->sc_set_guest_os = 1; + } +} + +void vmt_tick(void *xarg) { struct vmt_softc *sc = xarg; @@ -269,83 +352,249 @@ vmt_tick(void *xarg) struct timeval *guest = &sc->sc_sensor.tv; struct timeval host, diff; - size_t len; - u_int32_t rlen; - u_int16_t ack; - microtime(guest); bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; - frame.ecx.part.low = VM_CMD_GET_TIME; + frame.ecx.part.low = VM_CMD_GET_TIME_FULL; frame.edx.part.low = VM_PORT_CMD; vm_cmd(&frame); - if (frame.eax.word == 0xffffffff) { + if (frame.eax.word != 0xffffffff) { + host.tv_sec = ((uint64_t)frame.esi.word << 32) | frame.edx.word; + host.tv_usec = frame.ebx.word; + + timersub(guest, &host, &diff); + + sc->sc_sensor.value = (u_int64_t)diff.tv_sec * 1000000000LL + + (u_int64_t)diff.tv_usec * 1000LL; + sc->sc_sensor.status = SENSOR_S_OK; + } else { sc->sc_sensor.status = SENSOR_S_UNKNOWN; - goto out; } - host.tv_sec = frame.eax.word; - host.tv_usec = frame.ebx.word; + vmt_update_guest_info(sc); + vmt_update_guest_uptime(sc); - timersub(guest, &host, &diff); + timeout_add_sec(&sc->sc_tick, 15); +} - sc->sc_sensor.value = (u_int64_t)diff.tv_sec * 1000000000LL + - (u_int64_t)diff.tv_usec * 1000LL; - sc->sc_sensor.status = SENSOR_S_OK; +void +vmt_tclo_state_change_success(struct vmt_softc *sc, int success, char state) +{ + if (vm_rpc_send_rpci_tx(sc, "tools.os.statechange.status %d %d", + success, state) != 0) { + printf("%s: unable to send state change result\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } +} - if (strncmp(sc->sc_hostname, hostname, sizeof(sc->sc_hostname)) != 0) { - strlcpy(sc->sc_hostname, hostname, sizeof(sc->sc_hostname)); +void +vmt_do_shutdown(struct vmt_softc *sc) +{ + vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_HALT); + vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK); - len = snprintf(sc->sc_rpc_buf, VMT_RPC_BUFLEN, - "info-set guestinfo.ip %s ", "192.168.1.1"); -#ifdef DIAGNOSTIC - if (len > VMT_RPC_BUFLEN) - panic("vmt rpc buffer is too small"); -#endif + log(LOG_KERN | LOG_NOTICE, "Shutting down in response to request from VMware host\n"); + psignal(initproc, SIGUSR2); +} - if (vm_rpc_send(&sc->sc_rpc, sc->sc_rpc_buf, len) != 0) { - goto out; - } +void +vmt_do_reboot(struct vmt_softc *sc) +{ + vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_REBOOT); + vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK); - if (vm_rpc_get_length(&sc->sc_rpc, &rlen, &ack) != 0) { - goto out; - } + log(LOG_KERN | LOG_NOTICE, "Rebooting in response to request from VMware host\n"); + psignal(initproc, SIGINT); +} + +void +vmt_shutdown_hook(void *arg) +{ + struct vmt_softc *sc = arg; + + if (vm_rpc_send_rpci_tx(sc, "tools.capability.hgfs_server toolbox 0") != 0) { + printf("%s: failed to disable hgfs server capability\n", DEVNAME(sc)); + } + + if (vm_rpc_send(&sc->sc_tclo_rpc, NULL, 0) != 0) { + printf("%s: failed to send shutdown ping\n", DEVNAME(sc)); + } + + vm_rpc_close(&sc->sc_tclo_rpc); +} + +void +vmt_tclo_tick(void *xarg) +{ + struct vmt_softc *sc = xarg; + u_int32_t rlen; + u_int16_t ack; - bzero(sc->sc_rpc_buf, VMT_RPC_BUFLEN); - if (vm_rpc_get_data(&sc->sc_rpc, sc->sc_rpc_buf, - rlen, ack) != 0) { + /* reopen tclo channel if it's currently closed */ + if (sc->sc_tclo_rpc.channel == 0 && + sc->sc_tclo_rpc.cookie1 == 0 && + sc->sc_tclo_rpc.cookie2 == 0) { + if (vm_rpc_open(&sc->sc_tclo_rpc, VM_RPC_OPEN_TCLO) != 0) { + printf("%s: unable to reopen TCLO channel\n", DEVNAME(sc)); + timeout_add_sec(&sc->sc_tclo_tick, 15); return; } - if (sc->sc_rpc_buf[0] != '1' && sc->sc_rpc_buf[1] != ' ') { - printf("%s: setting hostname failed\n", DEVNAME(sc)); - return; + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_RESET_REPLY) != 0) { + printf("%s: failed to send reset reply\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + goto out; + } else { + sc->sc_rpc_error = 0; } + } - printf("%s: hostname set to %s\n", DEVNAME(sc), - sc->sc_hostname); + if (sc->sc_tclo_ping) { + if (vm_rpc_send(&sc->sc_tclo_rpc, NULL, 0) != 0) { + printf("%s: failed to send TCLO outgoing ping\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + goto out; + } } - if (vm_rpc_get_length(&sc->sc_rpc, &rlen, &ack) != 0) { - printf("%s: failed to get length of version reply\n", - DEVNAME(sc)); - return; + if (vm_rpc_get_length(&sc->sc_tclo_rpc, &rlen, &ack) != 0) { + printf("%s: failed to get length of incoming TCLO data\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + goto out; } - if (rlen == 0) + if (rlen == 0) { + sc->sc_tclo_ping = 1; goto out; + } - if (vm_rpc_get_data(&sc->sc_rpc, sc->sc_rpc_buf, rlen, ack) != 0) { - printf("%s: failed to get version reply\n", DEVNAME(sc)); - return; + if (rlen >= VMT_RPC_BUFLEN) { + rlen = VMT_RPC_BUFLEN - 1; } + if (vm_rpc_get_data(&sc->sc_tclo_rpc, sc->sc_rpc_buf, rlen, ack) != 0) { + printf("%s: failed to get incoming TCLO data\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + goto out; + } + sc->sc_tclo_ping = 0; + + if (strcmp(sc->sc_rpc_buf, "reset") == 0) { + + if (sc->sc_rpc_error != 0) { + printf("%s: resetting rpc\n", DEVNAME(sc)); + vm_rpc_close(&sc->sc_tclo_rpc); + /* reopen and send the reset reply next time around */ + goto out; + } + + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_RESET_REPLY) != 0) { + printf("%s: failed to send reset reply\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + + } else if (strcmp(sc->sc_rpc_buf, "ping") == 0) { + + vmt_update_guest_info(sc); + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { + printf("%s: error sending ping response\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + + } else if (strcmp(sc->sc_rpc_buf, "OS_Halt") == 0) { + vmt_do_shutdown(sc); + } else if (strcmp(sc->sc_rpc_buf, "OS_Reboot") == 0) { + vmt_do_reboot(sc); + } else if (strcmp(sc->sc_rpc_buf, "OS_PowerOn") == 0) { + vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_POWERON); + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { + printf("%s: error sending poweron response\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + } else if (strcmp(sc->sc_rpc_buf, "Capabilities_Register") == 0) { + + /* don't know if this is important at all */ + if (vm_rpc_send_rpci_tx(sc, "vmx.capability.unified_loop toolbox") != 0) { + printf("%s: unable to set unified loop\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + if (vm_rpci_response_successful(sc) == 0) { + printf("%s: host rejected unified loop setting\n", DEVNAME(sc)); + } + + /* the trailing space is apparently important here */ + if (vm_rpc_send_rpci_tx(sc, "tools.capability.statechange ") != 0) { + printf("%s: unable to send statechange capability\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + if (vm_rpci_response_successful(sc) == 0) { + printf("%s: host rejected statechange capability\n", DEVNAME(sc)); + } - printf("%s: \"%s\"\n", DEVNAME(sc), sc->sc_rpc_buf); + if (vm_rpc_send_rpci_tx(sc, "tools.set.version %u", VM_VERSION_UNMANAGED) != 0) { + printf("%s: unable to set tools version\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + + vmt_update_guest_uptime(sc); + + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { + printf("%s: error sending capabilities_register response\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + } else if (strcmp(sc->sc_rpc_buf, "Set_Option broadcastIP 1") == 0) { + struct ifnet *iface; + struct sockaddr_in *guest_ip; + + /* find first available ipv4 address */ + guest_ip = NULL; + TAILQ_FOREACH(iface, &ifnet, if_list) { + struct ifaddr *iface_addr; + + /* skip loopback */ + if (strncmp(iface->if_xname, "lo", 2) == 0 && + iface->if_xname[2] >= '0' && iface->if_xname[2] <= '9') { + continue; + } + + TAILQ_FOREACH(iface_addr, &iface->if_addrlist, ifa_list) { + if (iface_addr->ifa_addr->sa_family != AF_INET) { + continue; + } + + guest_ip = satosin(iface_addr->ifa_addr); + break; + } + } + + if (guest_ip != NULL) { + if (vm_rpc_send_rpci_tx(sc, "info-set guestinfo.ip %s", + inet_ntoa(guest_ip->sin_addr)) != 0) { + printf("%s: unable to send guest IP address\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { + printf("%s: error sending broadcastIP response\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + } else { + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR_IP_ADDR) != 0) { + printf("%s: error sending broadcastIP error response\n", + DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + } + } else { + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR) != 0) { + printf("%s: error sending unknown command reply\n", DEVNAME(sc)); + sc->sc_rpc_error = 1; + } + } out: - timeout_add_sec(&sc->sc_tick, 15); + timeout_add_sec(&sc->sc_tclo_tick, 1); } #define BACKDOOR_OP_I386(op, frame) \ @@ -424,13 +673,13 @@ vm_outs(struct vm_backdoor *frame) } int -vm_rpc_open(struct vm_rpc *rpc) +vm_rpc_open(struct vm_rpc *rpc, uint32_t proto) { struct vm_backdoor frame; bzero(&frame, sizeof(frame)); frame.eax.word = VM_MAGIC; - frame.ebx.word = VM_RPC_OPEN_RPCI_ENH; + frame.ebx.word = proto | VM_RPC_FLAG_COOKIE; frame.ecx.part.low = VM_CMD_RPC; frame.ecx.part.high = VM_RPC_OPEN; frame.edx.part.low = VM_PORT_CMD; @@ -439,6 +688,7 @@ vm_rpc_open(struct vm_rpc *rpc) vm_cmd(&frame); if (frame.ecx.part.high != 1 || frame.edx.part.low != 0) { + /* open-vm-tools retries without VM_RPC_FLAG_COOKIE here.. */ printf("vmware: open failed, eax=%08x, ecx=%08x, edx=%08x\n", frame.eax.word, frame.ecx.word, frame.edx.word); return EIO; @@ -499,7 +749,7 @@ vm_rpc_send(const struct vm_rpc *rpc, const uint8_t *buf, uint32_t length) vm_cmd(&frame); - if (frame.ecx.part.high == 0) { + if ((frame.ecx.part.high & VM_RPC_REPLY_SUCCESS) == 0) { printf("vmware: sending length failed, eax=%08x, ecx=%08x\n", frame.eax.word, frame.ecx.word); return EIO; @@ -513,7 +763,7 @@ vm_rpc_send(const struct vm_rpc *rpc, const uint8_t *buf, uint32_t length) frame.eax.word = VM_MAGIC; frame.ebx.word = VM_RPC_ENH_DATA; frame.ecx.word = length; - frame.edx.part.low = VM_PORT_RPC; // XXX we are here + frame.edx.part.low = VM_PORT_RPC; frame.edx.part.high = rpc->channel; frame.ebp.word = rpc->cookie1; frame.edi.word = rpc->cookie2; @@ -526,6 +776,7 @@ vm_rpc_send(const struct vm_rpc *rpc, const uint8_t *buf, uint32_t length) vm_outs(&frame); if (frame.ebx.word != VM_RPC_ENH_DATA) { + /* open-vm-tools retries on VM_RPC_REPLY_CHECKPOINT */ printf("vmware: send failed, ebx=%08x\n", frame.ebx.word); return EIO; } @@ -534,6 +785,12 @@ vm_rpc_send(const struct vm_rpc *rpc, const uint8_t *buf, uint32_t length) } int +vm_rpc_send_str(const struct vm_rpc *rpc, const uint8_t *str) +{ + return vm_rpc_send(rpc, str, strlen(str)); +} + +int vm_rpc_get_data(const struct vm_rpc *rpc, char *data, uint32_t length, uint16_t dataid) { @@ -556,6 +813,9 @@ vm_rpc_get_data(const struct vm_rpc *rpc, char *data, uint32_t length, vm_ins(&frame); + /* NUL-terminate the data */ + data[length] = '\0'; + if (frame.ebx.word != VM_RPC_ENH_DATA) { printf("vmware: get data failed, ebx=%08x\n", frame.ebx.word); @@ -601,18 +861,90 @@ vm_rpc_get_length(const struct vm_rpc *rpc, uint32_t *length, uint16_t *dataid) vm_cmd(&frame); - if (frame.ecx.part.high == 0) { + if ((frame.ecx.part.high & VM_RPC_REPLY_SUCCESS) == 0) { printf("vmware: get length failed, eax=%08x, ecx=%08x\n", frame.eax.word, frame.ecx.word); return EIO; } - - *length = frame.ebx.word; - *dataid = frame.edx.part.high; + if ((frame.ecx.part.high & VM_RPC_REPLY_DORECV) == 0) { + *length = 0; + *dataid = 0; + } else { + *length = frame.ebx.word; + *dataid = frame.edx.part.high; + } return 0; } +int +vm_rpci_response_successful(struct vmt_softc *sc) +{ + return (sc->sc_rpc_buf[0] == '1' && sc->sc_rpc_buf[1] == ' '); +} + +int +vm_rpc_send_rpci_tx_buf(struct vmt_softc *sc, const uint8_t *buf, uint32_t length) +{ + struct vm_rpc rpci; + u_int32_t rlen; + u_int16_t ack; + int result = 0; + + if (vm_rpc_open(&rpci, VM_RPC_OPEN_RPCI) != 0) { + printf("%s: rpci channel open failed\n", DEVNAME(sc)); + return EIO; + } + + if (vm_rpc_send(&rpci, sc->sc_rpc_buf, length) != 0) { + printf("%s: unable to send rpci command\n", DEVNAME(sc)); + result = EIO; + goto out; + } + + if (vm_rpc_get_length(&rpci, &rlen, &ack) != 0) { + printf("%s: failed to get length of rpci response data\n", DEVNAME(sc)); + result = EIO; + goto out; + } + + if (rlen > 0) { + if (rlen >= VMT_RPC_BUFLEN) { + rlen = VMT_RPC_BUFLEN - 1; + } + + if (vm_rpc_get_data(&rpci, sc->sc_rpc_buf, rlen, ack) != 0) { + printf("%s: failed to get rpci response data\n", DEVNAME(sc)); + result = EIO; + goto out; + } + } + +out: + if (vm_rpc_close(&rpci) != 0) { + printf("%s: unable to close rpci channel\n", DEVNAME(sc)); + } + + return result; +} + +int +vm_rpc_send_rpci_tx(struct vmt_softc *sc, const char *fmt, ...) +{ + va_list args; + int len; + + va_start(args, fmt); + len = vsnprintf(sc->sc_rpc_buf, VMT_RPC_BUFLEN, fmt, args); + va_end(args); + + if (len >= VMT_RPC_BUFLEN) { + printf("%s: rpci command didn't fit in buffer\n", DEVNAME(sc)); + return EIO; + } + + return vm_rpc_send_rpci_tx_buf(sc, sc->sc_rpc_buf, len); +} #if 0 struct vm_backdoor frame; @@ -643,3 +975,69 @@ vm_rpc_get_length(const struct vm_rpc *rpc, uint32_t *length, uint16_t *dataid) printf("edi 0x%08x\n", frame.edi.word); printf("esi 0x%08x\n", frame.esi.word); #endif + +/* + * Notes on tracing backdoor activity in vmware-guestd: + * + * - Find the addresses of the inl / rep insb / rep outsb + * instructions used to perform backdoor operations. + * One way to do this is to disassemble vmware-guestd: + * + * $ objdump -S /emul/freebsd/sbin/vmware-guestd > vmware-guestd.S + * + * and search for '<tab>in ' in the resulting file. The rep insb and + * rep outsb code is directly below that. + * + * - Run vmware-guestd under gdb, setting up breakpoints as follows: + * (the addresses shown here are the ones from VMware-server-1.0.10-203137, + * the last version that actually works in FreeBSD emulation on OpenBSD) + * + * break *0x805497b (address of 'in' instruction) + * commands 1 + * silent + * echo INOUT\n + * print/x $ecx + * print/x $ebx + * print/x $edx + * continue + * end + * break *0x805497c (address of instruction after 'in') + * commands 2 + * silent + * echo ===\n + * print/x $ecx + * print/x $ebx + * print/x $edx + * echo \n + * continue + * end + * break *0x80549b7 (address of instruction before 'rep insb') + * commands 3 + * silent + * set variable $inaddr = $edi + * set variable $incount = $ecx + * continue + * end + * break *0x80549ba (address of instruction after 'rep insb') + * commands 4 + * silent + * echo IN\n + * print $incount + * x/s $inaddr + * echo \n + * continue + * end + * break *0x80549fb (address of instruction before 'rep outsb') + * commands 5 + * silent + * echo OUT\n + * print $ecx + * x/s $esi + * echo \n + * continue + * end + * + * This will produce a log of the backdoor operations, including the + * data sent and received and the relevant register values. You can then + * match the register values to the various constants in this file. + */ |