diff options
author | Giampaolo Rodola' <g.rodola@gmail.com> | 2015-02-09 06:27:16 -0800 |
---|---|---|
committer | Giampaolo Rodola' <g.rodola@gmail.com> | 2015-02-09 06:27:16 -0800 |
commit | 2b983b5d294870a315292f5f006b717e5456b99a (patch) | |
tree | dfe35cd9f59a300c25a054ec2e064dc877dd9e16 | |
parent | ea8853644e34ef3036912df77e8f1615e9251e87 (diff) | |
parent | e34c853ea6d48fd4e888d70a1501b152884cd332 (diff) | |
download | psutil-2b983b5d294870a315292f5f006b717e5456b99a.tar.gz |
Merge pull request #588 from giampaolo/ifconfig-376
Ifconfig 376
-rw-r--r-- | docs/index.rst | 51 | ||||
-rw-r--r-- | examples/ifconfig.py | 66 | ||||
-rw-r--r-- | psutil/__init__.py | 43 | ||||
-rw-r--r-- | psutil/_common.py | 2 | ||||
-rw-r--r-- | psutil/_psbsd.py | 2 | ||||
-rw-r--r-- | psutil/_pslinux.py | 4 | ||||
-rw-r--r-- | psutil/_psosx.py | 2 | ||||
-rw-r--r-- | psutil/_pssunos.py | 2 | ||||
-rw-r--r-- | psutil/_psutil_posix.c | 175 | ||||
-rw-r--r-- | psutil/_psutil_posix.h | 1 | ||||
-rw-r--r-- | psutil/_psutil_windows.c | 174 | ||||
-rw-r--r-- | psutil/_psutil_windows.h | 1 | ||||
-rw-r--r-- | psutil/_pswindows.py | 2 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | test/_linux.py | 96 | ||||
-rw-r--r-- | test/test_memory_leaks.py | 3 | ||||
-rw-r--r-- | test/test_psutil.py | 53 |
17 files changed, 673 insertions, 6 deletions
diff --git a/docs/index.rst b/docs/index.rst index 29972f98..92d7fe73 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -200,7 +200,7 @@ Memory * **total**: total swap memory in bytes * **used**: used swap memory in bytes * **free**: free swap memory in bytes - * **percent**: the percentage usage + * **percent**: the percentage usage calculated as ``(total - available) / total * 100`` * **sin**: the number of bytes the system has swapped in from disk (cumulative) * **sout**: the number of bytes the system has swapped out from disk @@ -403,6 +403,47 @@ Network .. versionadded:: 2.1.0 +.. function:: net_if_addrs() + + Return the addresses associated to each NIC (network interface card) + installed on the system as a dictionary whose keys are the NIC names and + value is a list of namedtuples for each address assigned to the NIC. + Each namedtuple includes 4 fields: + + - **family** + - **address** + - **netmask** + - **broadcast** + + *family* can be either + `AF_INET <http://docs.python.org//library/socket.html#socket.AF_INET>`__, + `AF_INET6 <http://docs.python.org//library/socket.html#socket.AF_INET6>`__ + or :const:`psutil.AF_LINK`, which refers to a MAC address. + *address* is the primary address, *netmask* and *broadcast* may be ``None``. + Example:: + + >>> import psutil + >>> psutil.net_if_addrs() + {'lo': [snic(family=<AddressFamily.AF_INET: 2>, address='127.0.0.1', netmask='255.0.0.0', broadcast='127.0.0.1'), + snic(family=<AddressFamily.AF_INET6: 10>, address='::1', netmask='ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', broadcast=None), + snic(family=<AddressFamily.AF_PACKET: 17>, address='00:00:00:00:00:00', netmask=None, broadcast='00:00:00:00:00:00')], + 'wlan0': [snic(family=<AddressFamily.AF_INET: 2>, address='192.168.1.3', netmask='255.255.255.0', broadcast='192.168.1.255'), + snic(family=<AddressFamily.AF_INET6: 10>, address='fe80::c685:8ff:fe45:641%wlan0', netmask='ffff:ffff:ffff:ffff::', broadcast=None), + snic(family=<AddressFamily.AF_PACKET: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff')]} + >>> + + See also `examples/ifconfig.py <https://github.com/giampaolo/psutil/blob/master/examples/ifconfig.py>`__ + for an example application. + + .. note:: if you're interested in others families (e.g. AF_BLUETOOTH) you can + use the more powerful `netifaces <https://pypi.python.org/pypi/netifaces/>`__ + extension. + + .. note:: you can have more than one address of the same family associated + with each interface (that's why dict values are lists). + + *New in 3.0.0* + Other system info ----------------- @@ -1255,3 +1296,11 @@ Constants `man prlimit <http://linux.die.net/man/2/prlimit>`__ for futher information. Availability: Linux + +.. _const-aflink: +.. data:: AF_LINK + + Constant which identifies a MAC address associated with a network interface. + To be used in conjunction with :func:`psutil.net_if_addrs()`. + + *New in 3.0.0* diff --git a/examples/ifconfig.py b/examples/ifconfig.py new file mode 100644 index 00000000..7c47024d --- /dev/null +++ b/examples/ifconfig.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +A clone of 'ifconfig' on UNIX. + +$ python examples/ifconfig.py +lo: + IPv4 address : 127.0.0.1 + broadcast : 127.0.0.1 + netmask : 255.0.0.0 + IPv6 address : ::1 + netmask : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + HWADDR address : 00:00:00:00:00:00 + broadcast : 00:00:00:00:00:00 + +wlan0: + IPv4 address : 192.168.1.3 + broadcast : 192.168.1.255 + netmask : 255.255.255.0 + IPv6 address : fe80::c685:8ff:fe45:641%wlan0 + netmask : ffff:ffff:ffff:ffff:: + HWADDR address : c4:85:08:45:06:41 + broadcast : ff:ff:ff:ff:ff:ff + +docker0: + IPv4 address : 172.17.42.1 + broadcast : 172.17.42.1 + netmask : 255.255.0.0 + IPv6 address : fe80::ac6d:3aff:fe0d:a19c%docker0 + netmask : ffff:ffff:ffff:ffff:: + HWADDR address : ae:6d:3a:0d:a1:9c + broadcast : ff:ff:ff:ff:ff:ff +""" + +from __future__ import print_function +import socket + +import psutil + + +af_map = { + socket.AF_INET: 'IPv4', + socket.AF_INET6: 'IPv6', + psutil.AF_LINK: 'HWADDR', +} + + +def main(): + for nic, addrs in psutil.net_if_addrs().items(): + print("%s:" % (nic)) + for addr in addrs: + print(" %-8s" % af_map.get(addr.family, addr.family), end="") + print(" address : %s" % addr.address) + if addr.broadcast: + print(" broadcast : %s" % addr.broadcast) + if addr.netmask: + print(" netmask : %s" % addr.netmask) + print("") + + +if __name__ == '__main__': + main() diff --git a/psutil/__init__.py b/psutil/__init__.py index 73ecccc7..a08e6453 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -11,6 +11,7 @@ in Python. """ from __future__ import division + import collections import errno import functools @@ -31,7 +32,9 @@ from ._compat import PY3 as _PY3 from ._common import (deprecated_method as _deprecated_method, deprecated as _deprecated, sdiskio as _nt_sys_diskio, - snetio as _nt_sys_netio) + snetio as _nt_sys_netio, + snic as _nt_snic, + ) from ._common import (STATUS_RUNNING, # NOQA STATUS_SLEEPING, @@ -144,13 +147,14 @@ __all__ = [ "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE", + "AF_LINK", # classes "Process", "Popen", # functions "pid_exists", "pids", "process_iter", "wait_procs", # proc "virtual_memory", "swap_memory", # memory "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu - "net_io_counters", "net_connections", # network + "net_io_counters", "net_connections", "net_if_addrs", # network "disk_io_counters", "disk_partitions", "disk_usage", # disk "users", "boot_time", # others ] @@ -158,6 +162,7 @@ __all__.extend(_psplatform.__extra__all__) __author__ = "Giampaolo Rodola'" __version__ = "3.0.0" version_info = tuple([int(num) for num in __version__.split('.')]) +AF_LINK = _psplatform.AF_LINK _TOTAL_PHYMEM = None _POSIX = os.name == 'posix' _WINDOWS = os.name == 'nt' @@ -1814,6 +1819,40 @@ def net_connections(kind='inet'): return _psplatform.net_connections(kind) +def net_if_addrs(): + """Return the addresses associated to each NIC (network interface + card) installed on the system as a dictionary whose keys are the + NIC names and value is a list of namedtuples for each address + assigned to the NIC. Each namedtuple includes 4 fields: + + - family + - address + - netmask + - broadcast + + 'family' can be either socket.AF_INET, socket.AF_INET6 or + psutil.AF_LINK, which refers to a MAC address. + 'address' is the primary address, 'netmask' and 'broadcast' + may be None. + Note: you can have more than one address of the same family + associated with each interface. + """ + has_enums = sys.version_info >= (3, 4) + if has_enums: + import socket + rawlist = _psplatform.net_if_addrs() + rawlist.sort(key=lambda x: x[1]) # sort by family + ret = collections.defaultdict(list) + for name, fam, addr, mask, broadcast in rawlist: + if has_enums: + try: + fam = socket.AddressFamily(fam) + except ValueError: + pass + ret[name].append(_nt_snic(fam, addr, mask, broadcast)) + return dict(ret) + + # ===================================================================== # --- other system related functions # ===================================================================== diff --git a/psutil/_common.py b/psutil/_common.py index 2c17a994..cc223cf8 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -238,6 +238,8 @@ suser = namedtuple('suser', ['name', 'terminal', 'host', 'started']) # psutil.net_connections() sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', 'status', 'pid']) +# psutil.net_if_addrs() +snic = namedtuple('snic', ['family', 'address', 'netmask', 'broadcast']) # --- namedtuples for psutil.Process methods diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index cf2ee64c..87dfb7a6 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -50,6 +50,7 @@ TCP_STATUSES = { } PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK # extend base mem ntuple with BSD-specific memory metrics svmem = namedtuple( @@ -211,6 +212,7 @@ pid_exists = _psposix.pid_exists disk_usage = _psposix.disk_usage net_io_counters = cext.net_io_counters disk_io_counters = cext.disk_io_counters +net_if_addrs = cext_posix.net_if_addrs def wrap_exceptions(fun): diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index bfea5625..c21d089e 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -54,6 +54,7 @@ CLOCK_TICKS = os.sysconf("SC_CLK_TCK") PAGESIZE = os.sysconf("SC_PAGE_SIZE") BOOT_TIME = None # set later DEFAULT_ENCODING = sys.getdefaultencoding() +AF_LINK = socket.AF_PACKET # ioprio_* constants http://linux.die.net/man/2/ioprio_get IOPRIO_CLASS_NONE = 0 @@ -555,6 +556,9 @@ def net_io_counters(): return retdict +net_if_addrs = cext_posix.net_if_addrs + + # --- disks def disk_io_counters(): diff --git a/psutil/_psosx.py b/psutil/_psosx.py index 1fc6779f..141c9dae 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -24,6 +24,7 @@ __extra__all__ = [] # --- constants PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK # http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h TCP_STATUSES = { @@ -171,6 +172,7 @@ pid_exists = _psposix.pid_exists disk_usage = _psposix.disk_usage net_io_counters = cext.net_io_counters disk_io_counters = cext.disk_io_counters +net_if_addrs = cext_posix.net_if_addrs def wrap_exceptions(fun): diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py index 907e216a..69281a8c 100644 --- a/psutil/_pssunos.py +++ b/psutil/_pssunos.py @@ -25,6 +25,7 @@ from ._compat import PY3 __extra__all__ = ["CONN_IDLE", "CONN_BOUND"] PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') +AF_LINK = socket.AF_LINK CONN_IDLE = "IDLE" CONN_BOUND = "BOUND" @@ -73,6 +74,7 @@ TimeoutExpired = None disk_io_counters = cext.disk_io_counters net_io_counters = cext.net_io_counters disk_usage = _psposix.disk_usage +net_if_addrs = cext_posix.net_if_addrs def virtual_memory(): diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c index d020837c..2f96c1b7 100644 --- a/psutil/_psutil_posix.c +++ b/psutil/_psutil_posix.c @@ -10,6 +10,20 @@ #include <errno.h> #include <stdlib.h> #include <sys/resource.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <ifaddrs.h> + +#ifdef __linux +#include <netdb.h> +#include <linux/if_packet.h> +#endif // end linux + +#if defined(__FreeBSD__) || defined(__APPLE__) +#include <netdb.h> +#include <netinet/in.h> +#include <net/if_dl.h> +#endif #include "_psutil_posix.h" @@ -55,6 +69,160 @@ psutil_posix_setpriority(PyObject *self, PyObject *args) /* + * Translate a sockaddr struct into a Python string. + * Return None if address family is not AF_INET* or AF_PACKET. + */ +static PyObject * +psutil_convert_ipaddr(struct sockaddr *addr, int family) +{ + char buf[NI_MAXHOST]; + int err; + int addrlen; + int n; + size_t len; + const char *data; + char *ptr; + + if (addr == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + else if (family == AF_INET || family == AF_INET6) { + if (family == AF_INET) + addrlen = sizeof(struct sockaddr_in); + else + addrlen = sizeof(struct sockaddr_in6); + err = getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0, + NI_NUMERICHOST); + if (err != 0) { + // XXX we get here on FreeBSD when processing 'lo' / AF_INET6 + // broadcast. Not sure what to do other than returning None. + // ifconfig does not show anything BTW. + //PyErr_Format(PyExc_RuntimeError, gai_strerror(err)); + //return NULL; + Py_INCREF(Py_None); + return Py_None; + } + else { + return Py_BuildValue("s", buf); + } + } +#ifdef __linux + else if (family == AF_PACKET) { + struct sockaddr_ll *lladdr = (struct sockaddr_ll *)addr; + len = lladdr->sll_halen; + data = (const char *)lladdr->sll_addr; + } +#endif +#if defined(__FreeBSD__) || defined(__APPLE__) + else if (addr->sa_family == AF_LINK) { + // Note: prior to Python 3.4 socket module does not expose + // AF_LINK so we'll do. + struct sockaddr_dl *dladdr = (struct sockaddr_dl *)addr; + len = dladdr->sdl_alen; + data = LLADDR(dladdr); + } +#endif + else { + // unknown family + Py_INCREF(Py_None); + return Py_None; + } + + // AF_PACKET or AF_LINK + if (len > 0) { + ptr = buf; + for (n = 0; n < len; ++n) { + sprintf(ptr, "%02x:", data[n] & 0xff); + ptr += 3; + } + *--ptr = '\0'; + return Py_BuildValue("s", buf); + } + else { + Py_INCREF(Py_None); + return Py_None; + } +} + + +/* + * Return NICs information a-la ifconfig as a list of tuples. + */ +static PyObject* +psutil_net_if_addrs(PyObject* self, PyObject* args) +{ + struct ifaddrs *ifaddr, *ifa; + int family; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_address = NULL; + PyObject *py_netmask = NULL; + PyObject *py_broadcast = NULL; + + + if (getifaddrs(&ifaddr) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + family = ifa->ifa_addr->sa_family; + py_address = psutil_convert_ipaddr(ifa->ifa_addr, family); + // If the primary address can't be determined just skip it. + // I've never seen this happen on Linux but I did on FreeBSD. + if (py_address == Py_None) + continue; + if (py_address == NULL) + goto error; + py_netmask = psutil_convert_ipaddr(ifa->ifa_netmask, family); + if (py_netmask == NULL) + goto error; +#ifdef __linux + py_broadcast = psutil_convert_ipaddr(ifa->ifa_ifu.ifu_broadaddr, family); +#else + py_broadcast = psutil_convert_ipaddr(ifa->ifa_broadaddr, family); +#endif + if (py_broadcast == NULL) + goto error; + py_tuple = Py_BuildValue( + "(siOOO)", + ifa->ifa_name, + family, + py_address, + py_netmask, + py_broadcast + ); + + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_DECREF(py_address); + Py_DECREF(py_netmask); + Py_DECREF(py_broadcast); + } + + freeifaddrs(ifaddr); + return py_retlist; + +error: + if (ifaddr != NULL) + freeifaddrs(ifaddr); + Py_DECREF(py_retlist); + Py_XDECREF(py_tuple); + Py_XDECREF(py_address); + Py_XDECREF(py_netmask); + Py_XDECREF(py_broadcast); + return NULL; +} + + +/* * define the psutil C module methods and initialize the module. */ static PyMethodDef @@ -64,6 +232,8 @@ PsutilMethods[] = "Return process priority"}, {"setpriority", psutil_posix_setpriority, METH_VARARGS, "Set process priority"}, + {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS, + "Retrieve NICs information"}, {NULL, NULL, 0, NULL} }; @@ -118,6 +288,11 @@ void init_psutil_posix(void) #else PyObject *module = Py_InitModule("_psutil_posix", PsutilMethods); #endif + +#if defined(__FreeBSD__) || defined(__APPLE__) + PyModule_AddIntConstant(module, "AF_LINK", AF_LINK); +#endif + if (module == NULL) { INITERROR; } diff --git a/psutil/_psutil_posix.h b/psutil/_psutil_posix.h index 5a4681d1..91e524b0 100644 --- a/psutil/_psutil_posix.h +++ b/psutil/_psutil_posix.h @@ -6,5 +6,6 @@ #include <Python.h> +static PyObject* psutil_net_if_addrs(PyObject* self, PyObject* args); static PyObject* psutil_posix_getpriority(PyObject* self, PyObject* args); static PyObject* psutil_posix_setpriority(PyObject* self, PyObject* args); diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index 70d26867..0427bc1d 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -20,6 +20,7 @@ #include <winsock2.h> #include <iphlpapi.h> #include <wtsapi32.h> +#include <ws2tcpip.h> // Link with Iphlpapi.lib #pragma comment(lib, "IPHLPAPI.lib") @@ -2988,6 +2989,176 @@ error: } + +/* + * Return NICs addresses. + */ + +#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) +#define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) + +static PyObject * +psutil_net_if_addrs(PyObject *self, PyObject *args) +{ + DWORD dwSize = 0; + DWORD dwRetVal = 0; + unsigned int i = 0; + ULONG family; + LPVOID lpMsgBuf = NULL; + ULONG outBufLen = 0; + ULONG iterations = 0; + PCTSTR WSAAPI intRet; + char *ptr; + char buff[100]; + char ifname[MAX_PATH]; + DWORD bufflen = 100; + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_address = NULL; + PyObject *py_mac_address = NULL; + + + // allocate a 15 KB buffer to start with + outBufLen = 15000; + do { + pAddresses = (IP_ADAPTER_ADDRESSES *)MALLOC(outBufLen); + if (pAddresses == NULL) { + PyErr_NoMemory(); + goto error; + } + dwRetVal = GetAdaptersAddresses( + AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, pAddresses, &outBufLen); + if (dwRetVal == ERROR_BUFFER_OVERFLOW) { + FREE(pAddresses); + pAddresses = NULL; + } + else { + break; + } + iterations++; + } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (iterations < 3)); + + if (dwRetVal != NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "GetAdaptersAddresses failed"); + goto error; + } + + pCurrAddresses = pAddresses; + while (pCurrAddresses) { + pUnicast = pCurrAddresses->FirstUnicastAddress; + sprintf(ifname, "%wS", pCurrAddresses->FriendlyName); + + // MAC address + if (pCurrAddresses->PhysicalAddressLength != 0) { + ptr = buff; + *ptr = '\0'; + for (i = 0; i < (int) pCurrAddresses->PhysicalAddressLength; i++) { + if (i == (pCurrAddresses->PhysicalAddressLength - 1)) { + sprintf(ptr, "%.2X\n", + (int)pCurrAddresses->PhysicalAddress[i]); + } + else { + sprintf(ptr, "%.2X-", + (int)pCurrAddresses->PhysicalAddress[i]); + } + ptr += 3; + } + *--ptr = '\0'; + + py_mac_address = PyString_FromString(buff); + Py_INCREF(Py_None); + Py_INCREF(Py_None); + py_tuple = Py_BuildValue( + "(siOOO)", + ifname, + -1, // this will be converted later to AF_LINK + py_mac_address, + Py_None, + Py_None + ); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_DECREF(py_mac_address); + } + + // find out the IP address associated with the NIC + if (pUnicast != NULL) { + for (i = 0; pUnicast != NULL; i++) { + family = pUnicast->Address.lpSockaddr->sa_family; + if (family == AF_INET) { + struct sockaddr_in *sa_in = (struct sockaddr_in *) + pUnicast->Address.lpSockaddr; + intRet = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, + bufflen); + } + else if (family == AF_INET6) { + struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *) + pUnicast->Address.lpSockaddr; + intRet = inet_ntop(AF_INET6, &(sa_in6->sin6_addr), + buff, bufflen); + } + else { + // we should never get here + pUnicast = pUnicast->Next; + continue; + } + + if (intRet == NULL) { + PyErr_SetFromWindowsErr(GetLastError()); + goto error; + } + py_address = PyString_FromString(buff); + if (py_address == NULL) + goto error; + + Py_INCREF(Py_None); + Py_INCREF(Py_None); + py_tuple = Py_BuildValue( + "(siOOO)", + ifname, + family, + py_address, + Py_None, + Py_None + ); + + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_DECREF(py_address); + + pUnicast = pUnicast->Next; + } + } + + pCurrAddresses = pCurrAddresses->Next; + } + + if (pAddresses) + FREE(pAddresses); + + return py_retlist; + +error: + if (pAddresses) + FREE(pAddresses); + Py_DECREF(py_retlist); + Py_XDECREF(py_tuple); + Py_XDECREF(py_address); + return NULL; +} + + + // ------------------------ Python init --------------------------- static PyMethodDef @@ -3092,7 +3263,8 @@ PsutilMethods[] = "Return disk partitions."}, {"net_connections", psutil_net_connections, METH_VARARGS, "Return system-wide connections"}, - + {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS, + "Return NICs addresses."}, // --- windows API bindings {"win32_QueryDosDevice", psutil_win32_QueryDosDevice, METH_VARARGS, diff --git a/psutil/_psutil_windows.h b/psutil/_psutil_windows.h index 546704e3..862da9f2 100644 --- a/psutil/_psutil_windows.h +++ b/psutil/_psutil_windows.h @@ -60,6 +60,7 @@ static PyObject* psutil_pids(PyObject* self, PyObject* args); static PyObject* psutil_ppid_map(PyObject* self, PyObject* args); static PyObject* psutil_users(PyObject* self, PyObject* args); static PyObject* psutil_virtual_mem(PyObject* self, PyObject* args); +static PyObject* psutil_net_if_addrs(PyObject* self, PyObject* args); // --- windows API bindings diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index f0bf17b9..d3212b87 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -33,6 +33,7 @@ CONN_DELETE_TCB = "DELETE_TCB" WAIT_TIMEOUT = 0x00000102 # 258 in decimal ACCESS_DENIED_SET = frozenset([errno.EPERM, errno.EACCES, cext.ERROR_ACCESS_DENIED]) +AF_LINK = -1 TCP_STATUSES = { cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED, @@ -199,6 +200,7 @@ pid_exists = cext.pid_exists net_io_counters = cext.net_io_counters disk_io_counters = cext.disk_io_counters ppid_map = cext.ppid_map # not meant to be public +net_if_addrs = cext.net_if_addrs def wrap_exceptions(fun): @@ -78,7 +78,7 @@ if sys.platform.startswith("win32"): ], libraries=[ "psapi", "kernel32", "advapi32", "shell32", "netapi32", "iphlpapi", - "wtsapi32", + "wtsapi32", "ws2_32", ], # extra_compile_args=["/Z7"], # extra_link_args=["/DEBUG"] diff --git a/test/_linux.py b/test/_linux.py index 77f6da27..8f02b45b 100644 --- a/test/_linux.py +++ b/test/_linux.py @@ -7,16 +7,82 @@ """Linux specific tests. These are implicitly run by test_psutil.py.""" from __future__ import division +import array +import contextlib +import fcntl import os +import pprint import re +import socket +import struct import sys import time from test_psutil import POSIX, TOLERANCE, TRAVIS from test_psutil import (skip_on_not_implemented, sh, get_test_subprocess, - retry_before_failing, get_kernel_version, unittest) + retry_before_failing, get_kernel_version, unittest, + which) import psutil +from psutil._compat import PY3 + + +SIOCGIFADDR = 0x8915 +SIOCGIFCONF = 0x8912 +SIOCGIFHWADDR = 0x8927 + + +def get_ipv4_address(ifname): + ifname = ifname[:15] + if PY3: + ifname = bytes(ifname, 'ascii') + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + with contextlib.closing(s): + return socket.inet_ntoa( + fcntl.ioctl(s.fileno(), + SIOCGIFADDR, + struct.pack('256s', ifname))[20:24]) + + +def get_mac_address(ifname): + ifname = ifname[:15] + if PY3: + ifname = bytes(ifname, 'ascii') + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + with contextlib.closing(s): + info = fcntl.ioctl( + s.fileno(), SIOCGIFHWADDR, struct.pack('256s', ifname)) + if PY3: + def ord(x): + return x + else: + import __builtin__ + ord = __builtin__.ord + return ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1] + + +def get_nic_names(): + """Find the names of all available network interfaces.""" + # Thanks to: + # https://code.fluendo.com/flumotion/svn/flumotion/trunk/flumotion/common/ + # netutils.py + ptr_size = len(struct.pack('P', 0)) + size = 24 + 2 * (ptr_size) + max_possible = 128 + inbytes = max_possible * size + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + with contextlib.closing(s): + names = array.array('B', b'\0' * inbytes) + outbytes = struct.unpack('iP', fcntl.ioctl( + s.fileno(), + SIOCGIFCONF, + struct.pack('iP', inbytes, names.buffer_info()[0])))[0] + namestr = names.tostring() + ret = [namestr[i:i + size].split(b'\0', 1)[0] + for i in range(0, outbytes, size)] + if PY3: + ret = [x.decode() for x in ret] + return ret class LinuxSpecificTestCase(unittest.TestCase): @@ -139,6 +205,34 @@ class LinuxSpecificTestCase(unittest.TestCase): else: self.assertNotIn('guest_nice', fields) + def test_net_if_addrs_ips(self): + for name, addrs in psutil.net_if_addrs().items(): + for addr in addrs: + if addr.family == psutil.AF_LINK: + self.assertEqual(addr.address, get_mac_address(name)) + elif addr.family == socket.AF_INET: + self.assertEqual(addr.address, get_ipv4_address(name)) + # TODO: test for AF_INET6 family + + @unittest.skipUnless(which('ip'), "'ip' utility not available") + @unittest.skipIf(TRAVIS, "skipped on Travis") + def test_net_if_names(self): + out = sh("ip addr").strip() + nics = psutil.net_if_addrs() + found = 0 + for line in out.split('\n'): + line = line.strip() + if re.search("^\d+:", line): + found += 1 + name = line.split(':')[1].strip() + self.assertIn(name, nics.keys()) + self.assertEqual(len(nics), found, msg="%s\n---\n%s" % ( + pprint.pformat(nics), out)) + + def test_net_if_names_2(self): + self.assertEqual(sorted(get_nic_names()), + sorted(psutil.net_if_addrs())) + # --- tests for specific kernel versions @unittest.skipUnless( diff --git a/test/test_memory_leaks.py b/test/test_memory_leaks.py index 827f9d47..1441daa1 100644 --- a/test/test_memory_leaks.py +++ b/test/test_memory_leaks.py @@ -409,6 +409,9 @@ class TestModuleFunctionsLeaks(Base): def test_net_connections(self): self.execute('net_connections') + def test_net_if_addrs(self): + self.execute('net_if_addrs') + def test_main(): test_suite = unittest.TestSuite() diff --git a/test/test_psutil.py b/test/test_psutil.py index 6c2176da..efbcead1 100644 --- a/test/test_psutil.py +++ b/test/test_psutil.py @@ -304,6 +304,8 @@ def check_ip_address(addr, family): if not PY3: addr = unicode(addr) ipaddress.IPv6Address(addr) + elif family == psutil.AF_LINK: + assert re.match('([a-fA-F0-9]{2}[:|\-]?){6}', addr) is not None, addr else: raise ValueError("unknown family %r", family) @@ -1039,6 +1041,54 @@ class TestSystemAPIs(unittest.TestCase): self.assertTrue(key) check_ntuple(ret[key]) + def test_net_if_addrs(self): + nics = psutil.net_if_addrs() + assert nics, nics + + # Not reliable on all platforms (net_if_addrs() reports more + # interfaces). + # self.assertEqual(sorted(nics.keys()), + # sorted(psutil.net_io_counters(pernic=True).keys())) + + families = set([socket.AF_INET, AF_INET6, psutil.AF_LINK]) + for nic, addrs in nics.items(): + self.assertEqual(len(set(addrs)), len(addrs)) + for addr in addrs: + self.assertIsInstance(addr.family, int) + self.assertIsInstance(addr.address, str) + self.assertIsInstance(addr.netmask, (str, type(None))) + self.assertIsInstance(addr.broadcast, (str, type(None))) + self.assertIn(addr.family, families) + if sys.version_info >= (3, 4): + self.assertIsInstance(addr.family, socket.AddressFamily) + if addr.family == socket.AF_INET: + s = socket.socket(addr.family) + with contextlib.closing(s): + s.bind((addr.address, 0)) + elif addr.family == socket.AF_INET6: + info = socket.getaddrinfo( + addr.address, 0, socket.AF_INET6, socket.SOCK_STREAM, + 0, socket.AI_PASSIVE)[0] + af, socktype, proto, canonname, sa = info + s = socket.socket(af, socktype, proto) + with contextlib.closing(s): + s.bind(sa) + for ip in (addr.address, addr.netmask, addr.broadcast): + if ip is not None: + # TODO: skip AF_INET6 for now because I get: + # AddressValueError: Only hex digits permitted in + # u'c6f3%lxcbr0' in u'fe80::c8e0:fff:fe54:c6f3%lxcbr0' + if addr.family != AF_INET6: + check_ip_address(ip, addr.family) + + if BSD or OSX or SUNOS: + if hasattr(socket, "AF_LINK"): + self.assertEqual(psutil.AF_LINK, socket.AF_LINK) + elif LINUX: + self.assertEqual(psutil.AF_LINK, socket.AF_PACKET) + elif WINDOWS: + self.assertEqual(psutil.AF_LINK, -1) + @unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'), '/proc/diskstats not available on this linux version') def test_disk_io_counters(self): @@ -2666,6 +2716,9 @@ class TestExampleScripts(unittest.TestCase): def test_netstat(self): self.assert_stdout('netstat.py') + def test_ifconfig(self): + self.assert_stdout('ifconfig.py') + def test_pmap(self): self.assert_stdout('pmap.py', args=str(os.getpid())) |