From: Michael Tremer Date: Sun, 1 Sep 2013 12:54:32 +0000 (+0200) Subject: Rewrite hypervisor detection. X-Git-Tag: v2.1.8~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e1ca6671bc3496a41469a52208f930b6551a4c5d;p=oddments%2Ffireinfo.git Rewrite hypervisor detection. Reduce the size of code and drop checks for para-virtualization. All this should decrease false positives. --- diff --git a/fireinfo/bios.py b/fireinfo/bios.py new file mode 100644 index 0000000..56e3af0 --- /dev/null +++ b/fireinfo/bios.py @@ -0,0 +1,50 @@ +#!/usr/bin/python +############################################################################### +# # +# Fireinfo # +# Copyright (C) 2013 IPFire Team (www.ipfire.org) # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +import os.path + +DMI_VENDORS = [ + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor", +] + +class BIOS(object): + def __init__(self, system): + self.system = system + + def check_vendor(self, vendor, startswith=True): + for file in DMI_VENDORS: + if not os.path.exists(file): + continue + + with open(file, "r") as f: + v = f.read() + + # Strip the vendor string. + v = v.strip() + + if startswith and v.startswith(vendor): + return True + elif v == vendor: + return True + + return False diff --git a/fireinfo/hypervisor.py b/fireinfo/hypervisor.py index 9a3fb09..0c07cfa 100644 --- a/fireinfo/hypervisor.py +++ b/fireinfo/hypervisor.py @@ -24,7 +24,7 @@ import system class Hypervisor(object): def __init__(self): - self.__info = _fireinfo.get_hypervisor() + self.__hypervisor = _fireinfo.detect_hypervisor() @property def system(self): @@ -41,109 +41,79 @@ class Hypervisor(object): """ Returns the name of the hypervisor vendor. """ - if not self.virtual: - return None + # Citrix Xen says it is Microsoft Hv. + if self.__hypervisor == "Microsoft" and self.system.bios_vendor == "Xen": + return "Xen" # Some of the hypervisors can be detected in a right way. # We can return them at this place. - if self.__info["hypervisor"] in ("Xen", "VMWare", "KVM"): - return self.__info["hypervisor"] - - # Citrix Xen says it is Microsoft Hv. - if self.__info["hypervisor"] == "Microsoft" and \ - self.system.bios_vendor == "Xen": + if self.__hypervisor: + return self.__hypervisor + + # Check DMI and BIOS information... + if self.__bios_is_bochs(): + return "Bochs" + elif self.__bios_is_microsoft(): + return "Microsoft" + elif self.__bios_is_qemu(): + return "Qemu" + elif self.__bios_is_virtualbox(): + return "VirtualBox" + elif self.__bios_is_vmware(): + return "VMWare" + elif self.__bios_is_xen(): return "Xen" - if not self.__info["hypervisor"]: - # On VMWare systems, the bios vendor string contains "VMWare". - if self.__is_hypervisor_vmware(): - return "VMWare" - - # VirtualBox got "innotek GmbH" as bios vendor. - elif self.__is_hypervisor_virtualbox(): - return "VirtualBox" - - # Check for qemu. - elif self.__is_hypervisor_qemu(): - return "Qemu" - - # Check for Microsoft. - elif self.__is_hypervisor_microsoft(): - return "Microsoft" - - return "unknown" - - @property - def type(self): - """ - Returns if the host is running in full virt mode or - if it is running in a paravirtualized environment. - """ - if not self.virtual: - return None - - if self.__info["virtype"]: - return self.__info["virtype"] - - if self.vendor in ("Qemu", "KVM", "VirtualBox", "VMWare"): - return "full" - - return "unknown" - @property def virtual(self): """ Returns true if the host is running in a virtual environment. Otherwise: false. """ - return _fireinfo.is_virtualized() or \ - "hypervisor" in self.system.cpu.flags or \ - self.__is_hypervisor_virtualbox() or \ - self.__is_hypervisor_vmware() or \ - self.__is_hypervisor_qemu() or \ - self.__is_hypervisor_microsoft() - - def __is_hypervisor_virtualbox(self): + if self.vendor: + return True + + return False + + def __bios_is_bochs(self): """ - Check for virtualbox hypervisor by comparing the bios vendor string - to "innotek GmbH". + Check for Bochs emulator. """ - return self.system.bios_vendor == "innotek GmbH" + return self.system.bios.check_vendor("Bochs") - def __is_hypervisor_vmware(self): + def __bios_is_microsoft(self): """ - Check for the VMWare hypervisor by the VMWare Hypervisor port check. - - http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 + Check for Microsoft hypervisor. """ - if self.system.vendor: - return self.system.vendor.startswith("VMware") - - # XXX We should use _fireinfo.vmware_hypervisor_port_check() here, too. - # This currently segfaults (and I have no clue why) on VMware player. + return self.system.bios.check_vendor("Microsoft Corporation") - def __is_hypervisor_qemu(self): + def __bios_is_qemu(self): """ - Check for old qemu emulator. + Check for qemu emulator. """ - if self.system.bios_vendor: - return self.system.bios_vendor == "Bochs" + return self.system.bios.check_vendor("QEMU") - return False - - def __is_hypervisor_microsoft(self): + def __bios_is_virtualbox(self): """ - Check for Microsoft hypervisor. + Check for virtualbox hypervisor by comparing the bios vendor string + to "innotek GmbH". """ - if self.system.vendor: - return "Microsoft" in self.system.vendor + return self.system.bios.check_vendor("innotek GmbH") + + def __bios_is_vmware(self): + if self.system.bios.check_vendor("VMware-"): + return True + elif self.system.bios.check_vendor("VMW"): + return True return False + def __bios_is_xen(self): + return self.system.bios.check_vendor("Xen") + if __name__ == "__main__": h = Hypervisor() print "Vendor:", h.vendor - print "Type:", h.type print "Virtual:", h.virtual diff --git a/fireinfo/system.py b/fireinfo/system.py index 7cca92e..0a60bcc 100644 --- a/fireinfo/system.py +++ b/fireinfo/system.py @@ -26,6 +26,7 @@ import string import _fireinfo +import bios import cpu import device import hypervisor @@ -78,6 +79,8 @@ class System(object): __metaclass__ = Singleton def __init__(self): + self.bios = bios.BIOS(self) + # find all devices self.devices = [] self.scan() @@ -149,7 +152,6 @@ class System(object): # Only append hypervisor information if we are virtualized. if self.virtual: p["hypervisor"] = { - "type" : self.hypervisor.type, "vendor" : self.hypervisor.vendor, } diff --git a/src/fireinfo.c b/src/fireinfo.c index 25b5333..77616a1 100644 --- a/src/fireinfo.c +++ b/src/fireinfo.c @@ -18,141 +18,78 @@ #include +#include #include #include #include +#include #include -/* - Big parts of this were taken from - http://git.kernel.org/?p=utils/util-linux-ng/util-linux-ng.git;a=blob;f=sys-utils/lscpu.c -*/ - -/* /sys paths */ -#define _PATH_PROC_XEN "/proc/xen" -#define _PATH_PROC_XENCAP _PATH_PROC_XEN "/capabilities" -#define _PATH_PROC_PCIDEVS "/proc/bus/pci/devices" - -/* Used for the vmware hypervisor port detection */ -#define VMWARE_HYPERVISOR_MAGIC 0x564D5868 -#define VMWARE_HYPERVISOR_PORT 0x5658 - -#define VMWARE_PORT_CMD_GETVERSION 10 - -/* virtualization types */ -enum { - VIRT_NONE = 0, - VIRT_PARA, - VIRT_FULL -}; -const char *virt_types[] = { - [VIRT_NONE] = "none", - [VIRT_PARA] = "para", - [VIRT_FULL] = "full" -}; - /* hypervisor vendors */ -enum { - HYPER_NONE = 0, +enum hypervisors { + HYPER_NONE = 0, HYPER_XEN, HYPER_KVM, HYPER_MSHV, - HYPER_VMWARE -}; -const char *hv_vendors[] = { - [HYPER_NONE] = NULL, - [HYPER_XEN] = "Xen", - [HYPER_KVM] = "KVM", - [HYPER_MSHV] = "Microsoft", - [HYPER_VMWARE] = "VMWare" + HYPER_VMWARE, + HYPER_OTHER, + HYPER_LAST /* for loop - must be last*/ }; -struct hypervisor_desc { - int hyper; /* hypervisor vendor ID */ - int virtype; /* VIRT_PARA|FULL|NONE ? */ +const char *hypervisor_ids[] = { + [HYPER_NONE] = NULL, + [HYPER_XEN] = "XenVMMXenVMM", + [HYPER_KVM] = "KVMKVMKVM", + /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ + [HYPER_MSHV] = "Microsoft Hv", + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + [HYPER_VMWARE] = "VMwareVMware", + [HYPER_OTHER] = NULL }; -static size_t sysrootlen; -static char pathbuf[PATH_MAX]; - -static FILE *path_fopen(const char *mode, const char *path, ...) - __attribute__ ((__format__ (__printf__, 2, 3))); -static int path_exist(const char *path, ...) - __attribute__ ((__format__ (__printf__, 1, 2))); - -static const char * -path_vcreate(const char *path, va_list ap) -{ - if (sysrootlen) - vsnprintf(pathbuf + sysrootlen, - sizeof(pathbuf) - sysrootlen, path, ap); - else - vsnprintf(pathbuf, sizeof(pathbuf), path, ap); - return pathbuf; -} - -static FILE * -path_vfopen(const char *mode, const char *path, va_list ap) -{ - const char *p = path_vcreate(path, ap); - - return fopen(p, mode); -} - -static FILE * -path_fopen(const char *mode, const char *path, ...) -{ - FILE *fd; - va_list ap; - - va_start(ap, path); - fd = path_vfopen(mode, path, ap); - va_end(ap); - - return fd; -} +const char *hypervisor_vendors[] = { + [HYPER_NONE] = NULL, + [HYPER_XEN] = "Xen", + [HYPER_KVM] = "KVM", + [HYPER_MSHV] = "Microsoft", + [HYPER_VMWARE] = "VMWare", + [HYPER_OTHER] = "other" +}; -static int -path_exist(const char *path, ...) -{ - va_list ap; - const char *p; +#define NEWLINE "\n\r" - va_start(ap, path); - p = path_vcreate(path, ap); - va_end(ap); +char *truncate_nl(char *s) { + assert(s); - return access(p, F_OK) == 0; + s[strcspn(s, NEWLINE)] = 0; + return s; } -static int -has_pci_device(int vendor, int device) -{ - FILE *f; - int num, fn, ven, dev; - int res = 1; +int read_one_line_file(const char *filename, char **line) { + assert(filename); + assert(line); - f = path_fopen("r", _PATH_PROC_PCIDEVS); + FILE *f = NULL; + f = fopen(filename, "re"); if (!f) - return 0; + return -errno; - /* for more details about bus/pci/devices format see - * drivers/pci/proc.c in linux kernel - */ - while(fscanf(f, "%02x%02x\t%04x%04x\t%*[^\n]", - &num, &fn, &ven, &dev) == 4) { + char t[2048]; + if (!fgets(t, sizeof(t), f)) { + if (ferror(f)) + return errno ? -errno : -EIO; - if (ven == vendor && dev == device) - goto found; + t[0] = 0; } - res = 0; -found: - fclose(f); - return res; -} + char *c = strdup(t); + if (!c) + return -ENOMEM; + truncate_nl(c); -#if defined(__x86_64__) || defined(__i386__) + *line = c; + return 0; +} /* * This CPUID leaf returns the information about the hypervisor. @@ -161,222 +98,96 @@ found: */ #define HYPERVISOR_INFO_LEAF 0x40000000 -static inline void -cpuid(unsigned int op, unsigned int *eax, unsigned int *ebx, - unsigned int *ecx, unsigned int *edx) -{ - __asm__( -#if defined(__PIC__) && defined(__i386__) - /* x86 PIC cannot clobber ebx -- gcc bitches */ - "pushl %%ebx;" - "cpuid;" - "movl %%ebx, %%esi;" - "popl %%ebx;" - : "=S" (*ebx), -#else - "cpuid;" - : "=b" (*ebx), -#endif - "=a" (*eax), - "=c" (*ecx), - "=d" (*edx) - : "1" (op), "c"(0)); -} - -static void -read_hypervisor_cpuid(struct hypervisor_desc *desc) -{ - unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0; - char hyper_vendor_id[13]; - - memset(hyper_vendor_id, 0, sizeof(hyper_vendor_id)); - - cpuid(HYPERVISOR_INFO_LEAF, &eax, &ebx, &ecx, &edx); - memcpy(hyper_vendor_id + 0, &ebx, 4); - memcpy(hyper_vendor_id + 4, &ecx, 4); - memcpy(hyper_vendor_id + 8, &edx, 4); - hyper_vendor_id[12] = '\0'; - - if (!hyper_vendor_id[0]) - return; +int detect_hypervisor(int *hypervisor) { +#if defined(__x86_64__) || defined(__i386__) + /* Try high-level hypervisor sysfs file first: */ + char *hvtype = NULL; + int r = read_one_line_file("/sys/hypervisor/type", &hvtype); + if (r >= 0) { + if (strcmp(hvtype, "xen") == 0) { + *hypervisor = HYPER_XEN; + return 1; + } + } else if (r != -ENOENT) + return r; - if (!strncmp("XenVMMXenVMM", hyper_vendor_id, 12)) - desc->hyper = HYPER_XEN; - else if (!strncmp("KVMKVMKVM", hyper_vendor_id, 9)) - desc->hyper = HYPER_KVM; - else if (!strncmp("Microsoft Hv", hyper_vendor_id, 12)) - desc->hyper = HYPER_MSHV; - else if (!strncmp("VMwareVMware", hyper_vendor_id, 12)) - desc->hyper = HYPER_VMWARE; -} + /* http://lwn.net/Articles/301888/ */ -#else /* ! __x86_64__ */ -static void -read_hypervisor_cpuid(struct hypervisor_desc *desc) -{ -} +#if defined(__amd64__) +#define REG_a "rax" +#define REG_b "rbx" +#elif defined(__i386__) +#define REG_a "eax" +#define REG_b "ebx" #endif -static void -read_hypervisor(struct hypervisor_desc *desc) -{ - read_hypervisor_cpuid(desc); - - if (desc->hyper) - /* hvm */ - desc->virtype = VIRT_FULL; - - else if (path_exist(_PATH_PROC_XEN)) { - /* Xen para-virt or dom0 */ - FILE *fd = path_fopen("r", _PATH_PROC_XENCAP); - int dom0 = 0; - - if (fd) { - char buf[256]; + uint32_t eax = 1; + uint32_t ecx; + union { + uint32_t sig32[3]; + char text[13]; + } sig = {}; + + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=c" (ecx) + : "0" (eax) + ); - if (fscanf(fd, "%s", buf) == 1 && - !strcmp(buf, "control_d")) - dom0 = 1; - fclose(fd); + bool has_hypervisor = !!(ecx & 0x80000000U); + + if (has_hypervisor) { + /* There is a hypervisor, see what it is... */ + eax = 0x40000000U; + __asm__ __volatile__ ( + " push %%"REG_b" \n\t" + " cpuid \n\t" + " mov %%ebx, %1 \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) + : "0" (eax) + ); + sig.text[12] = '\0'; + + *hypervisor = HYPER_OTHER; + + int id; + for (id = HYPER_NONE + 1; id < HYPER_LAST; id++) { + if (strcmp(hypervisor_vendors[id], sig.text) == 0) { + *hypervisor = id; + break; + } } - desc->virtype = dom0 ? VIRT_NONE : VIRT_PARA; - desc->hyper = HYPER_XEN; - } else if (has_pci_device(0x5853, 0x0001)) { - /* Xen full-virt on non-x86_64 */ - desc->hyper = HYPER_XEN; - desc->virtype = VIRT_FULL; + return 1; } -} - -static void -read_harddisk_serial(char *device, char *serial) { - static struct hd_driveid hd; - int fd; - - if ((fd = open(device, O_RDONLY | O_NONBLOCK)) < 0) { - return; - } - - if (!ioctl(fd, HDIO_GET_IDENTITY, &hd)) { - strncpy(serial, (const char *)hd.serial_no, 20); - } -} - -#if defined(__x86_64__) || defined(__i386__) -static bool -is_virtualized() { - unsigned int eax, ebx, ecx, edx; - - cpuid(0x1, &eax, &ebx, &ecx, &edx); - - /* - Bitwise detection of the 31st bit. - This indicates if a host runs in a virtual environment. - */ - if (ecx & (1<<31)) - return true; - - return false; -} - -void -hypervisor_port(unsigned int cmd, unsigned int *eax, unsigned int *ebx, - unsigned int *ecx, unsigned int *edx) -{ - __asm__( -#if defined(__PIC__) && defined(__i386__) - /* x86 PIC really cannot clobber ebx */ - "pushl %%ebx;" - "inl (%%dx);" - "movl %%ebx, %%esi;" - "popl %%ebx;" - : "=S" (*ebx), -#else - "inl (%%dx);" - : "=b" (*ebx), #endif - "=a" (*eax), - "=c" (*ecx), - "=d" (*edx) - : "0" (VMWARE_HYPERVISOR_MAGIC), - "1" (cmd), - "2" (VMWARE_HYPERVISOR_PORT), - "3" (UINT_MAX) - : "memory" - ); -} -#else -static bool -is_virtualized() { - /* - Always return false, because other architectures - do not support the virtualization bit. - */ - return false; -} - -void -hypervisor_port(unsigned int cmd, unsigned int *eax, unsigned int *ebx, - unsigned int *ecx, unsigned int *edx) -{ + return 0; } -#endif - -int -hypervisor_port_check(void) { - uint32_t eax, ebx, ecx, edx; - hypervisor_port(VMWARE_PORT_CMD_GETVERSION, &eax, &ebx, &ecx, &edx); - - if (ebx == VMWARE_HYPERVISOR_MAGIC) - return 1; // Success - running under VMware - else - return 0; -} static PyObject * -do_get_hypervisor() { +do_detect_hypervisor() { /* Get hypervisor from the cpuid command. */ - struct hypervisor_desc _desc, *desc = &_desc; - memset(desc, 0, sizeof(*desc)); - - read_hypervisor(desc); + int hypervisor = HYPER_NONE; - PyObject *d = PyDict_New(); - PyObject *o; + int r = detect_hypervisor(&hypervisor); + if (r >= 1) { + const char* hypervisor_vendor = hypervisor_vendors[hypervisor]; + if (!hypervisor_vendor) + Py_RETURN_NONE; - /* Hypervisor */ - if (desc->hyper == HYPER_NONE) { - o = Py_None; - } else { - o = PyString_FromString((const char *)hv_vendors[desc->hyper]); + return PyString_FromString(hypervisor_vendor); } - PyDict_SetItemString(d, "hypervisor", o); - /* Virtualization type */ - if (desc->virtype == VIRT_NONE) { - o = Py_None; - } else { - o = PyString_FromString((const char *)virt_types[desc->virtype]); - } - PyDict_SetItemString(d, "virtype", o); - - return d; -} - -static PyObject * -do_is_virtualized() { - /* - Python wrapper around is_virtualized(). - */ - - if (is_virtualized()) - return Py_True; - - return Py_False; + Py_RETURN_NONE; } static PyObject * @@ -384,39 +195,30 @@ do_get_harddisk_serial(PyObject *o, PyObject *args) { /* Python wrapper around read_harddisk_serial. */ - - char serial[21]; - memset(serial, 0, sizeof(serial)); - + static struct hd_driveid hd; + int fd; char *device; + if (!PyArg_ParseTuple(args, "s", &device)) return NULL; - read_harddisk_serial(device, serial); - - if (serial[0]) - return PyString_FromString(serial); - - return Py_None; -} + if ((fd = open(device, O_RDONLY | O_NONBLOCK)) < 0) + return NULL; -static PyObject * -do_hypervisor_port_check() { - /* - Python wrapper around hypervisor_port_check(). - */ + if (!ioctl(fd, HDIO_GET_IDENTITY, &hd)) { + char serial[21]; + strncpy(serial, (const char *)hd.serial_no, sizeof(serial)); - if (hypervisor_port_check()) - return Py_True; + if (serial[0]) + return PyString_FromString(serial); + } - return Py_False; + Py_RETURN_NONE; } static PyMethodDef fireinfoModuleMethods[] = { - { "get_hypervisor", (PyCFunction) do_get_hypervisor, METH_NOARGS, NULL }, - { "is_virtualized", (PyCFunction) do_is_virtualized, METH_NOARGS, NULL }, + { "detect_hypervisor", (PyCFunction) do_detect_hypervisor, METH_NOARGS, NULL }, { "get_harddisk_serial", (PyCFunction) do_get_harddisk_serial, METH_VARARGS, NULL }, - { "vmware_hypervisor_port_check", (PyCFunction) do_hypervisor_port_check, METH_NOARGS, NULL }, { NULL, NULL, 0, NULL } }; @@ -424,4 +226,6 @@ void init_fireinfo(void) { PyObject *m; m = Py_InitModule("_fireinfo", fireinfoModuleMethods); + if (m == NULL) + return; }