1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297 |
- /* $OpenBSD: vmt.c,v 1.3 2015/07/28 09:48:52 reyk Exp $ */
- /*
- * Copyright (c) 2007 David Crawshaw <david@zentus.com>
- * Copyright (c) 2008 David Gwynne <dlg@openbsd.org>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
- #if !defined(__i386__) && !defined(__amd64__)
- #error vmt(4) is only supported on i386 and amd64
- #endif
- /*
- * Protocol reverse engineered by Ken Kato:
- * http://chitchat.at.infoseek.co.jp/vmware/backdoor.html
- */
- #include <sys/param.h>
- #include <sys/systm.h>
- #include <sys/kernel.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 <net/if_var.h>
- #include <netinet/in.h>
- #include <dev/pv/pvvar.h>
- #include <dev/rndvar.h>
- /* "The" magic number, always occupies the EAX register. */
- #define VM_MAGIC 0x564D5868
- /* Port numbers, passed on EDX.LOW . */
- #define VM_PORT_CMD 0x5658
- #define VM_PORT_RPC 0x5659
- /* Commands, passed on ECX.LOW. */
- #define VM_CMD_GET_SPEED 0x01
- #define VM_CMD_APM 0x02
- #define VM_CMD_GET_MOUSEPOS 0x04
- #define VM_CMD_SET_MOUSEPOS 0x05
- #define VM_CMD_GET_CLIPBOARD_LEN 0x06
- #define VM_CMD_GET_CLIPBOARD 0x07
- #define VM_CMD_SET_CLIPBOARD_LEN 0x08
- #define VM_CMD_SET_CLIPBOARD 0x09
- #define VM_CMD_GET_VERSION 0x0a
- #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_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 */ /* 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
- #define VM_RPC_SET_LENGTH 0x01
- #define VM_RPC_SET_DATA 0x02
- #define VM_RPC_GET_LENGTH 0x03
- #define VM_RPC_GET_DATA 0x04
- #define VM_RPC_GET_END 0x05
- #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_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
- #define VM_STATE_CHANGE_RESUME 4
- #define VM_STATE_CHANGE_SUSPEND 5
- /* 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 {
- struct {
- uint16_t low;
- uint16_t high;
- } part;
- uint32_t word;
- #ifdef __amd64__
- struct {
- uint32_t low;
- uint32_t high;
- } words;
- uint64_t quad;
- #endif
- } __packed;
- /* A register frame. */
- struct vm_backdoor {
- union vm_reg eax;
- union vm_reg ebx;
- union vm_reg ecx;
- union vm_reg edx;
- union vm_reg esi;
- union vm_reg edi;
- union vm_reg ebp;
- } __packed;
- /* RPC context. */
- struct vm_rpc {
- uint16_t channel;
- uint32_t cookie1;
- uint32_t cookie2;
- };
- struct vmt_softc {
- struct device sc_dev;
- 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;
- char sc_hostname[MAXHOSTNAMELEN];
- };
- #ifdef VMT_DEBUG
- #define DPRINTF(_arg...) printf(_arg)
- #else
- #define DPRINTF(_arg...) do {} while(0)
- #endif
- #define DEVNAME(_s) ((_s)->sc_dev.dv_xname)
- void vm_cmd(struct vm_backdoor *);
- 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 *, 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_probe_cmd(struct vm_backdoor *, uint16_t);
- 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_shutdown(void *);
- void vmt_update_guest_info(struct vmt_softc *);
- void vmt_update_guest_uptime(struct vmt_softc *);
- void vmt_tick(void *);
- void vmt_resume(void);
- int vmt_match(struct device *, void *, void *);
- void vmt_attach(struct device *, struct device *, void *);
- int vmt_activate(struct device *, int);
- void vmt_tclo_tick(void *);
- int vmt_tclo_process(struct vmt_softc *, const char *);
- void vmt_tclo_reset(struct vmt_softc *);
- void vmt_tclo_ping(struct vmt_softc *);
- void vmt_tclo_halt(struct vmt_softc *);
- void vmt_tclo_reboot(struct vmt_softc *);
- void vmt_tclo_poweron(struct vmt_softc *);
- void vmt_tclo_suspend(struct vmt_softc *);
- void vmt_tclo_resume(struct vmt_softc *);
- void vmt_tclo_capreg(struct vmt_softc *);
- void vmt_tclo_broadcastip(struct vmt_softc *);
- int vmt_probe(void);
- struct vmt_tclo_rpc {
- const char *name;
- void (*cb)(struct vmt_softc *);
- } vmt_tclo_rpc[] = {
- /* Keep sorted by name (case-sensitive) */
- { "Capabilities_Register", vmt_tclo_capreg },
- { "OS_Halt", vmt_tclo_halt },
- { "OS_PowerOn", vmt_tclo_poweron },
- { "OS_Reboot", vmt_tclo_reboot },
- { "OS_Resume", vmt_tclo_resume },
- { "OS_Suspend", vmt_tclo_suspend },
- { "Set_Option broadcastIP 1", vmt_tclo_broadcastip },
- { "ping", vmt_tclo_ping },
- { "reset", vmt_tclo_reset },
- { NULL },
- #if 0
- /* Various unsupported commands */
- { "Set_Option autohide 0" },
- { "Set_Option copypaste 1" },
- { "Set_Option enableDnD 1" },
- { "Set_Option enableMessageBusTunnel 0" },
- { "Set_Option linkRootHgfsShare 0" },
- { "Set_Option mapRootHgfsShare 0" },
- { "Set_Option synctime 1" },
- { "Set_Option synctime.period 0" },
- { "Set_Option time.synchronize.tools.enable 1" },
- { "Set_Option time.synchronize.tools.percentCorrection 0" },
- { "Set_Option time.synchronize.tools.slewCorrection 1" },
- { "Set_Option time.synchronize.tools.startup 1" },
- { "Set_Option toolScripts.afterPowerOn 1" },
- { "Set_Option toolScripts.afterResume 1" },
- { "Set_Option toolScripts.beforePowerOff 1" },
- { "Set_Option toolScripts.beforeSuspend 1" },
- { "Time_Synchronize 0" },
- { "Vix_1_Relayed_Command \"38cdcae40e075d66\"" },
- #endif
- };
- struct cfattach vmt_ca = {
- sizeof(struct vmt_softc),
- vmt_match,
- vmt_attach,
- NULL,
- vmt_activate
- };
- struct cfdriver vmt_cd = {
- NULL,
- "vmt",
- DV_DULL
- };
- extern char hostname[MAXHOSTNAMELEN];
- void
- vmt_probe_cmd(struct vm_backdoor *frame, uint16_t cmd)
- {
- bzero(frame, sizeof(*frame));
- (frame->eax).word = VM_MAGIC;
- (frame->ebx).word = ~VM_MAGIC;
- (frame->ecx).part.low = cmd;
- (frame->ecx).part.high = 0xffff;
- (frame->edx).part.low = VM_PORT_CMD;
- (frame->edx).part.high = 0;
- vm_cmd(frame);
- }
- int
- vmt_probe(void)
- {
- struct vm_backdoor frame;
- vmt_probe_cmd(&frame, VM_CMD_GET_VERSION);
- if (frame.eax.word == 0xffffffff ||
- frame.ebx.word != VM_MAGIC)
- return (0);
- vmt_probe_cmd(&frame, VM_CMD_GET_SPEED);
- if (frame.eax.word == VM_MAGIC)
- return (0);
- return (1);
- }
- int
- vmt_match(struct device *parent, void *match, void *aux)
- {
- struct pv_attach_args *pva = aux;
- struct pvbus_hv *hv = &pva->pva_hv[PVBUS_VMWARE];
- if (hv->hv_base == 0)
- return (0);
- if (!vmt_probe())
- return (0);
- return (1);
- }
- void
- vmt_attach(struct device *parent, struct device *self, void *aux)
- {
- struct vmt_softc *sc = (struct vmt_softc *)self;
- printf("\n");
- sc->sc_rpc_buf = malloc(VMT_RPC_BUFLEN, M_DEVBUF, M_NOWAIT);
- if (sc->sc_rpc_buf == NULL) {
- printf("%s: unable to allocate buffer for RPC\n",
- DEVNAME(sc));
- return;
- }
- if (vm_rpc_open(&sc->sc_tclo_rpc, VM_RPC_OPEN_TCLO) != 0) {
- printf("%s: failed to open backdoor RPC channel "
- "(TCLO protocol)\n", DEVNAME(sc));
- goto free;
- }
- /* 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;
- }
- strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
- sizeof(sc->sc_sensordev.xname));
- sc->sc_sensor.type = SENSOR_TIMEDELTA;
- sc->sc_sensor.status = SENSOR_S_UNKNOWN;
- sensor_attach(&sc->sc_sensordev, &sc->sc_sensor);
- sensordev_install(&sc->sc_sensordev);
- timeout_set(&sc->sc_tick, vmt_tick, sc);
- if (mountroothook_establish(vmt_tick, sc) == NULL)
- DPRINTF("%s: unable to establish tick\n", DEVNAME(sc));
- timeout_set(&sc->sc_tclo_tick, vmt_tclo_tick, sc);
- timeout_add_sec(&sc->sc_tclo_tick, 1);
- sc->sc_tclo_ping = 1;
- return;
- free:
- free(sc->sc_rpc_buf, M_DEVBUF, 0);
- }
- void
- vmt_resume(void)
- {
- struct vm_backdoor frame;
- extern void rdrand(void *);
- bzero(&frame, sizeof(frame));
- frame.eax.word = VM_MAGIC;
- frame.ecx.part.low = VM_CMD_GET_TIME_FULL;
- frame.edx.part.low = VM_PORT_CMD;
- vm_cmd(&frame);
- rdrand(NULL);
- add_true_randomness(frame.eax.word);
- add_true_randomness(frame.esi.word);
- add_true_randomness(frame.edx.word);
- add_true_randomness(frame.ebx.word);
- resume_randomness(NULL, 0);
- }
- int
- vmt_activate(struct device *self, int act)
- {
- int rv = 0;
- switch (act) {
- case DVACT_POWERDOWN:
- vmt_shutdown(self);
- break;
- case DVACT_RESUME:
- vmt_resume();
- break;
- }
- return (rv);
- }
- 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 %lld00",
- VM_GUEST_INFO_UPTIME, (long long)time_uptime) != 0) {
- DPRINTF("%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) {
- DPRINTF("%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) {
- DPRINTF("%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) {
- DPRINTF("%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;
- struct vm_backdoor frame;
- struct timeval *guest = &sc->sc_sensor.tv;
- struct timeval host, diff;
- microtime(guest);
- bzero(&frame, sizeof(frame));
- frame.eax.word = VM_MAGIC;
- frame.ecx.part.low = VM_CMD_GET_TIME_FULL;
- frame.edx.part.low = VM_PORT_CMD;
- vm_cmd(&frame);
- 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;
- }
- vmt_update_guest_info(sc);
- vmt_update_guest_uptime(sc);
- timeout_add_sec(&sc->sc_tick, 15);
- }
- 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) {
- DPRINTF("%s: unable to send state change result\n",
- DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- }
- 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);
- suspend_randomness();
- log(LOG_KERN | LOG_NOTICE,
- "Shutting down in response to request from VMware host\n");
- prsignal(initprocess, SIGUSR2);
- }
- 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);
- suspend_randomness();
- log(LOG_KERN | LOG_NOTICE,
- "Rebooting in response to request from VMware host\n");
- prsignal(initprocess, SIGINT);
- }
- void
- vmt_shutdown(void *arg)
- {
- struct vmt_softc *sc = arg;
- if (vm_rpc_send_rpci_tx(sc,
- "tools.capability.hgfs_server toolbox 0") != 0) {
- DPRINTF("%s: failed to disable hgfs server capability\n",
- DEVNAME(sc));
- }
- if (vm_rpc_send(&sc->sc_tclo_rpc, NULL, 0) != 0) {
- DPRINTF("%s: failed to send shutdown ping\n", DEVNAME(sc));
- }
- vm_rpc_close(&sc->sc_tclo_rpc);
- }
- void
- vmt_tclo_reset(struct vmt_softc *sc)
- {
- if (sc->sc_rpc_error != 0) {
- DPRINTF("%s: resetting rpc\n", DEVNAME(sc));
- vm_rpc_close(&sc->sc_tclo_rpc);
- /* reopen and send the reset reply next time around */
- sc->sc_rpc_error = 1;
- return;
- }
- if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_RESET_REPLY) != 0) {
- DPRINTF("%s: failed to send reset reply\n", DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- }
- void
- vmt_tclo_ping(struct vmt_softc *sc)
- {
- vmt_update_guest_info(sc);
- if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) {
- DPRINTF("%s: error sending ping response\n", DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- }
- void
- vmt_tclo_halt(struct vmt_softc *sc)
- {
- vmt_do_shutdown(sc);
- }
- void
- vmt_tclo_reboot(struct vmt_softc *sc)
- {
- vmt_do_reboot(sc);
- }
- void
- vmt_tclo_poweron(struct vmt_softc *sc)
- {
- 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) {
- DPRINTF("%s: error sending poweron response\n", DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- }
- void
- vmt_tclo_suspend(struct vmt_softc *sc)
- {
- log(LOG_KERN | LOG_NOTICE,
- "VMware guest entering suspended state\n");
- suspend_randomness();
- vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_SUSPEND);
- if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) {
- DPRINTF("%s: error sending suspend response\n", DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- }
- void
- vmt_tclo_resume(struct vmt_softc *sc)
- {
- log(LOG_KERN | LOG_NOTICE,
- "VMware guest resuming from suspended state\n");
- /* force guest info update */
- sc->sc_hostname[0] = '\0';
- sc->sc_set_guest_os = 0;
- vmt_update_guest_info(sc);
- vmt_resume();
- vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_RESUME);
- if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) {
- DPRINTF("%s: error sending resume response\n", DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- }
- void
- vmt_tclo_capreg(struct vmt_softc *sc)
- {
- /* don't know if this is important at all */
- if (vm_rpc_send_rpci_tx(sc,
- "vmx.capability.unified_loop toolbox") != 0) {
- DPRINTF("%s: unable to set unified loop\n", DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- if (vm_rpci_response_successful(sc) == 0) {
- DPRINTF("%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) {
- DPRINTF("%s: unable to send statechange capability\n",
- DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- if (vm_rpci_response_successful(sc) == 0) {
- DPRINTF("%s: host rejected statechange capability\n",
- DEVNAME(sc));
- }
- if (vm_rpc_send_rpci_tx(sc, "tools.set.version %u",
- VM_VERSION_UNMANAGED) != 0) {
- DPRINTF("%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) {
- DPRINTF("%s: error sending capabilities_register"
- " response\n", DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- }
- void
- vmt_tclo_broadcastip(struct vmt_softc *sc)
- {
- 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) {
- char ip[INET_ADDRSTRLEN];
- inet_ntop(AF_INET, &guest_ip->sin_addr, ip, sizeof(ip));
- if (vm_rpc_send_rpci_tx(sc, "info-set guestinfo.ip %s",
- ip) != 0) {
- DPRINTF("%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) {
- DPRINTF("%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) {
- DPRINTF("%s: error sending broadcastIP"
- " error response\n", DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- }
- }
- int
- vmt_tclo_process(struct vmt_softc *sc, const char *name)
- {
- int i;
- /* Search for rpc command and call handler */
- for (i = 0; vmt_tclo_rpc[i].name != NULL; i++) {
- if (strcmp(vmt_tclo_rpc[i].name, sc->sc_rpc_buf) == 0) {
- vmt_tclo_rpc[i].cb(sc);
- return (0);
- }
- }
- DPRINTF("%s: unknown command: \"%s\"\n", DEVNAME(sc), name);
- return (-1);
- }
- void
- vmt_tclo_tick(void *xarg)
- {
- struct vmt_softc *sc = xarg;
- u_int32_t rlen;
- u_int16_t ack;
- int delay;
- /* By default, poll every second for new messages */
- delay = 1;
- /* 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) {
- DPRINTF("%s: unable to reopen TCLO channel\n",
- DEVNAME(sc));
- timeout_add_sec(&sc->sc_tclo_tick, 15);
- return;
- }
- if (vm_rpc_send_str(&sc->sc_tclo_rpc,
- VM_RPC_RESET_REPLY) != 0) {
- DPRINTF("%s: failed to send reset reply\n",
- DEVNAME(sc));
- sc->sc_rpc_error = 1;
- goto out;
- } else {
- sc->sc_rpc_error = 0;
- }
- }
- if (sc->sc_tclo_ping) {
- if (vm_rpc_send(&sc->sc_tclo_rpc, NULL, 0) != 0) {
- DPRINTF("%s: failed to send TCLO outgoing ping\n",
- DEVNAME(sc));
- sc->sc_rpc_error = 1;
- goto out;
- }
- }
- if (vm_rpc_get_length(&sc->sc_tclo_rpc, &rlen, &ack) != 0) {
- DPRINTF("%s: failed to get length of incoming TCLO data\n",
- DEVNAME(sc));
- sc->sc_rpc_error = 1;
- goto out;
- }
- if (rlen == 0) {
- sc->sc_tclo_ping = 1;
- goto out;
- }
- 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) {
- DPRINTF("%s: failed to get incoming TCLO data\n", DEVNAME(sc));
- sc->sc_rpc_error = 1;
- goto out;
- }
- sc->sc_tclo_ping = 0;
- /* The VM host can queue multiple messages; continue without delay */
- delay = 0;
- if (vmt_tclo_process(sc, sc->sc_rpc_buf) != 0) {
- if (vm_rpc_send_str(&sc->sc_tclo_rpc,
- VM_RPC_REPLY_ERROR) != 0) {
- DPRINTF("%s: error sending unknown command reply\n",
- DEVNAME(sc));
- sc->sc_rpc_error = 1;
- }
- }
- if (sc->sc_rpc_error == 1) {
- /* On error, give time to recover and wait a second */
- delay = 1;
- }
- out:
- timeout_add_sec(&sc->sc_tclo_tick, delay);
- }
- #define BACKDOOR_OP_I386(op, frame) \
- __asm__ volatile ( \
- "pushal;" \
- "pushl %%eax;" \
- "movl 0x18(%%eax), %%ebp;" \
- "movl 0x14(%%eax), %%edi;" \
- "movl 0x10(%%eax), %%esi;" \
- "movl 0x0c(%%eax), %%edx;" \
- "movl 0x08(%%eax), %%ecx;" \
- "movl 0x04(%%eax), %%ebx;" \
- "movl 0x00(%%eax), %%eax;" \
- op \
- "xchgl %%eax, 0x00(%%esp);" \
- "movl %%ebp, 0x18(%%eax);" \
- "movl %%edi, 0x14(%%eax);" \
- "movl %%esi, 0x10(%%eax);" \
- "movl %%edx, 0x0c(%%eax);" \
- "movl %%ecx, 0x08(%%eax);" \
- "movl %%ebx, 0x04(%%eax);" \
- "popl 0x00(%%eax);" \
- "popal;" \
- ::"a"(frame) \
- )
- #define BACKDOOR_OP_AMD64(op, frame) \
- __asm__ volatile ( \
- "pushq %%rbp; \n\t" \
- "pushq %%rax; \n\t" \
- "movq 0x30(%%rax), %%rbp; \n\t" \
- "movq 0x28(%%rax), %%rdi; \n\t" \
- "movq 0x20(%%rax), %%rsi; \n\t" \
- "movq 0x18(%%rax), %%rdx; \n\t" \
- "movq 0x10(%%rax), %%rcx; \n\t" \
- "movq 0x08(%%rax), %%rbx; \n\t" \
- "movq 0x00(%%rax), %%rax; \n\t" \
- op "\n\t" \
- "xchgq %%rax, 0x00(%%rsp); \n\t" \
- "movq %%rbp, 0x30(%%rax); \n\t" \
- "movq %%rdi, 0x28(%%rax); \n\t" \
- "movq %%rsi, 0x20(%%rax); \n\t" \
- "movq %%rdx, 0x18(%%rax); \n\t" \
- "movq %%rcx, 0x10(%%rax); \n\t" \
- "movq %%rbx, 0x08(%%rax); \n\t" \
- "popq 0x00(%%rax); \n\t" \
- "popq %%rbp; \n\t" \
- : /* No outputs. */ : "a" (frame) \
- /* No pushal on amd64 so warn gcc about the clobbered registers. */ \
- : "rbx", "rcx", "rdx", "rdi", "rsi", "cc", "memory" \
- )
- #ifdef __i386__
- #define BACKDOOR_OP(op, frame) BACKDOOR_OP_I386(op, frame)
- #else
- #define BACKDOOR_OP(op, frame) BACKDOOR_OP_AMD64(op, frame)
- #endif
- void
- vm_cmd(struct vm_backdoor *frame)
- {
- BACKDOOR_OP("inl %%dx, %%eax;", frame);
- }
- void
- vm_ins(struct vm_backdoor *frame)
- {
- BACKDOOR_OP("cld;\n\trep insb;", frame);
- }
- void
- vm_outs(struct vm_backdoor *frame)
- {
- BACKDOOR_OP("cld;\n\trep outsb;", frame);
- }
- int
- 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 = 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;
- frame.edx.part.high = 0;
- vm_cmd(&frame);
- if (frame.ecx.part.high != 1 || frame.edx.part.low != 0) {
- /* open-vm-tools retries without VM_RPC_FLAG_COOKIE here.. */
- DPRINTF("vmware: open failed, eax=%08x, ecx=%08x, edx=%08x\n",
- frame.eax.word, frame.ecx.word, frame.edx.word);
- return EIO;
- }
- rpc->channel = frame.edx.part.high;
- rpc->cookie1 = frame.esi.word;
- rpc->cookie2 = frame.edi.word;
- return 0;
- }
- int
- vm_rpc_close(struct vm_rpc *rpc)
- {
- struct vm_backdoor frame;
- bzero(&frame, sizeof(frame));
- frame.eax.word = VM_MAGIC;
- frame.ebx.word = 0;
- frame.ecx.part.low = VM_CMD_RPC;
- frame.ecx.part.high = VM_RPC_CLOSE;
- frame.edx.part.low = VM_PORT_CMD;
- frame.edx.part.high = rpc->channel;
- frame.edi.word = rpc->cookie2;
- frame.esi.word = rpc->cookie1;
- vm_cmd(&frame);
- if (frame.ecx.part.high == 0 || frame.ecx.part.low != 0) {
- DPRINTF("vmware: close failed, eax=%08x, ecx=%08x\n",
- frame.eax.word, frame.ecx.word);
- return EIO;
- }
- rpc->channel = 0;
- rpc->cookie1 = 0;
- rpc->cookie2 = 0;
- return 0;
- }
- int
- vm_rpc_send(const struct vm_rpc *rpc, const uint8_t *buf, uint32_t length)
- {
- struct vm_backdoor frame;
- /* Send the length of the command. */
- bzero(&frame, sizeof(frame));
- frame.eax.word = VM_MAGIC;
- frame.ebx.word = length;
- frame.ecx.part.low = VM_CMD_RPC;
- frame.ecx.part.high = VM_RPC_SET_LENGTH;
- frame.edx.part.low = VM_PORT_CMD;
- frame.edx.part.high = rpc->channel;
- frame.esi.word = rpc->cookie1;
- frame.edi.word = rpc->cookie2;
- vm_cmd(&frame);
- if ((frame.ecx.part.high & VM_RPC_REPLY_SUCCESS) == 0) {
- DPRINTF("vmware: sending length failed, eax=%08x, ecx=%08x\n",
- frame.eax.word, frame.ecx.word);
- return EIO;
- }
- if (length == 0)
- return 0; /* Only need to poke once if command is null. */
- /* Send the command using enhanced RPC. */
- bzero(&frame, sizeof(frame));
- frame.eax.word = VM_MAGIC;
- frame.ebx.word = VM_RPC_ENH_DATA;
- frame.ecx.word = length;
- frame.edx.part.low = VM_PORT_RPC;
- frame.edx.part.high = rpc->channel;
- frame.ebp.word = rpc->cookie1;
- frame.edi.word = rpc->cookie2;
- #ifdef __amd64__
- frame.esi.quad = (uint64_t)buf;
- #else
- frame.esi.word = (uint32_t)buf;
- #endif
- vm_outs(&frame);
- if (frame.ebx.word != VM_RPC_ENH_DATA) {
- /* open-vm-tools retries on VM_RPC_REPLY_CHECKPOINT */
- DPRINTF("vmware: send failed, ebx=%08x\n", frame.ebx.word);
- return EIO;
- }
- return 0;
- }
- 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)
- {
- struct vm_backdoor frame;
- /* Get data using enhanced RPC. */
- bzero(&frame, sizeof(frame));
- frame.eax.word = VM_MAGIC;
- frame.ebx.word = VM_RPC_ENH_DATA;
- frame.ecx.word = length;
- frame.edx.part.low = VM_PORT_RPC;
- frame.edx.part.high = rpc->channel;
- frame.esi.word = rpc->cookie1;
- #ifdef __amd64__
- frame.edi.quad = (uint64_t)data;
- #else
- frame.edi.word = (uint32_t)data;
- #endif
- frame.ebp.word = rpc->cookie2;
- vm_ins(&frame);
- /* NUL-terminate the data */
- data[length] = '\0';
- if (frame.ebx.word != VM_RPC_ENH_DATA) {
- DPRINTF("vmware: get data failed, ebx=%08x\n",
- frame.ebx.word);
- return EIO;
- }
- /* Acknowledge data received. */
- bzero(&frame, sizeof(frame));
- frame.eax.word = VM_MAGIC;
- frame.ebx.word = dataid;
- frame.ecx.part.low = VM_CMD_RPC;
- frame.ecx.part.high = VM_RPC_GET_END;
- frame.edx.part.low = VM_PORT_CMD;
- frame.edx.part.high = rpc->channel;
- frame.esi.word = rpc->cookie1;
- frame.edi.word = rpc->cookie2;
- vm_cmd(&frame);
- if (frame.ecx.part.high == 0) {
- DPRINTF("vmware: ack data failed, eax=%08x, ecx=%08x\n",
- frame.eax.word, frame.ecx.word);
- return EIO;
- }
- return 0;
- }
- int
- vm_rpc_get_length(const struct vm_rpc *rpc, uint32_t *length, uint16_t *dataid)
- {
- struct vm_backdoor frame;
- bzero(&frame, sizeof(frame));
- frame.eax.word = VM_MAGIC;
- frame.ebx.word = 0;
- frame.ecx.part.low = VM_CMD_RPC;
- frame.ecx.part.high = VM_RPC_GET_LENGTH;
- frame.edx.part.low = VM_PORT_CMD;
- frame.edx.part.high = rpc->channel;
- frame.esi.word = rpc->cookie1;
- frame.edi.word = rpc->cookie2;
- vm_cmd(&frame);
- if ((frame.ecx.part.high & VM_RPC_REPLY_SUCCESS) == 0) {
- DPRINTF("vmware: get length failed, eax=%08x, ecx=%08x\n",
- frame.eax.word, frame.ecx.word);
- return EIO;
- }
- 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) {
- DPRINTF("%s: rpci channel open failed\n", DEVNAME(sc));
- return EIO;
- }
- if (vm_rpc_send(&rpci, sc->sc_rpc_buf, length) != 0) {
- DPRINTF("%s: unable to send rpci command\n", DEVNAME(sc));
- result = EIO;
- goto out;
- }
- if (vm_rpc_get_length(&rpci, &rlen, &ack) != 0) {
- DPRINTF("%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) {
- DPRINTF("%s: failed to get rpci response data\n",
- DEVNAME(sc));
- result = EIO;
- goto out;
- }
- }
- out:
- if (vm_rpc_close(&rpci) != 0) {
- DPRINTF("%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) {
- DPRINTF("%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;
- bzero(&frame, sizeof(frame));
- frame.eax.word = VM_MAGIC;
- frame.ecx.part.low = VM_CMD_GET_VERSION;
- frame.edx.part.low = VM_PORT_CMD;
- printf("\n");
- printf("eax 0x%08x\n", frame.eax.word);
- printf("ebx 0x%08x\n", frame.ebx.word);
- printf("ecx 0x%08x\n", frame.ecx.word);
- printf("edx 0x%08x\n", frame.edx.word);
- printf("ebp 0x%08x\n", frame.ebp.word);
- printf("edi 0x%08x\n", frame.edi.word);
- printf("esi 0x%08x\n", frame.esi.word);
- vm_cmd(&frame);
- printf("-\n");
- printf("eax 0x%08x\n", frame.eax.word);
- printf("ebx 0x%08x\n", frame.ebx.word);
- printf("ecx 0x%08x\n", frame.ecx.word);
- printf("edx 0x%08x\n", frame.edx.word);
- printf("ebp 0x%08x\n", frame.ebp.word);
- 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.
- */
|