Skip to content

Commit

Permalink
#250: implement 'iconfig' on Linux (port from old mercurial branch)
Browse files Browse the repository at this point in the history
  • Loading branch information
giampaolo committed Jun 4, 2014
1 parent f7f2080 commit 6bb8093
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 2 deletions.
25 changes: 23 additions & 2 deletions psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"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
]
Expand All @@ -57,7 +57,8 @@
from psutil._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 _snic)

from psutil._common import (STATUS_RUNNING, # NOQA
STATUS_SLEEPING,
Expand Down Expand Up @@ -1793,6 +1794,26 @@ def net_connections(kind='inet'):
return _psplatform.net_connections(kind)


def net_if_addrs():
"""Return all NICs installed on the system as a dictionary whose
keys are NIC names and value is a namedtuple including:
- family (one of the socket.AF_* constant)
- address
- netmask
- broadcast
address, netmask and broadcast may be None in case NIC address is
not assigned.
"""
rawlist = _psplatform.net_if_addrs()
rawlist.sort(key=lambda x: x[1]) # sort by family
ret = defaultdict(list)
for name, fam, addr, mask, broadcast in rawlist:
ret[name].append(_snic(fam, addr, mask, broadcast))
return dict(ret)


# =====================================================================
# --- other system related functions
# =====================================================================
Expand Down
2 changes: 2 additions & 0 deletions psutil/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ def isfile_strict(path):
# 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
Expand Down
3 changes: 3 additions & 0 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,9 @@ def net_io_counters():
return retdict


net_if_addrs = _psutil_posix.net_if_addrs


# --- disks

def disk_io_counters():
Expand Down
164 changes: 164 additions & 0 deletions psutil/_psutil_posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -55,6 +69,153 @@ 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 PyString_FromString(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) {
// XXX socket module does not expose AF_LINK
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 PyString_FromString(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) {
family = ifa->ifa_addr->sa_family;

py_address = psutil_convert_ipaddr(ifa->ifa_addr, family);
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.
*/
Expand All @@ -65,6 +226,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}
};

Expand Down Expand Up @@ -119,6 +282,7 @@ void init_psutil_posix(void)
#else
PyObject *module = Py_InitModule("_psutil_posix", PsutilMethods);
#endif

if (module == NULL) {
INITERROR;
}
Expand Down
1 change: 1 addition & 0 deletions psutil/_psutil_posix.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
3 changes: 3 additions & 0 deletions test/test_memory_leaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,9 @@ def test_users(self):
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()
Expand Down
39 changes: 39 additions & 0 deletions test/test_psutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,42 @@ def check_ntuple(nt):
self.assertTrue(key)
check_ntuple(ret[key])

def test_net_if_addrs(self):
nics = psutil.net_if_addrs()
assert nics, nics

self.assertEqual(sorted(nics.keys()),
sorted(psutil.net_io_counters(pernic=True).keys()))

families = [getattr(socket, x) for x in dir(socket)
if x.startswith('AF_')]
# if psutil.AF_LINK:
# families.append(psutil.AF_LINK)
for nic, addrs in nics.items():
self.assertEqual(len(set(addrs)), len(addrs))
for addr in addrs:
assert isinstance(addr.family, int), addr
assert isinstance(addr.address, (str, types.NoneType)), addr
assert isinstance(addr.netmask, (str, types.NoneType)), addr
assert isinstance(addr.broadcast, (str, types.NoneType)), addr
assert addr.family in families, (addr, families)
if addr.family == socket.AF_INET:
s = socket.socket(addr.family)
try:
s.bind((addr.address, 0))
finally:
s.close()
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)
try:
s.bind(sa)
finally:
s.close()

@unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'),
'/proc/diskstats not available on this linux version')
def test_disk_io_counters(self):
Expand Down Expand Up @@ -2666,6 +2702,9 @@ def test_who(self):
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()))

Expand Down

0 comments on commit 6bb8093

Please sign in to comment.