Skip to content

Commit

Permalink
#376: unify AF_LINK constant across all platforms + update doc and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
giampaolo committed Feb 7, 2015
1 parent d507407 commit 80b04a5
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 40 deletions.
51 changes: 28 additions & 23 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -405,35 +405,44 @@ Network

.. function:: net_if_addrs()

Return all NICs installed on the system as a dictionary whose keys are NIC
names and value is a namedtuple including:
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* is one of the ``socket.AF_*`` constants. *address*, *netmask* and
*broadcast* may be ``None`` in case the NIC address is not assigned.
On OSX and FreeBSD :const:`psutil.AF_LINK` is also available and it refers
to a MAC address.
On (all?) other UNIX variants ``socket.AF_PACKET`` refers to the NIC MAC
address.
Note: the supported address families are limited to AF_INET, AF_INET6,
AF_PACKET (Linux) and AF_LINK (OSX / FreeBSD). If you're interested in others
(e.g. AF_BLUETOOTH) you can use the more powerful
`netifaces <https://pypi.python.org/pypi/netifaces/>`__ extension.
Example:
*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=2, address='127.0.0.1', netmask='255.0.0.0', broadcast='127.0.0.1'),
snic(family=10, address='::1', netmask='ffff:ffff:ffff:ffff', broadcast=None)],
'wlan0': [snic(family=2, address='192.168.0.10', netmask='255.255.255.0', broadcast='192.168.0.255'),
snic(family=10, address='2a02:8109:83c0:224c::5', netmask='ffff:ffff:ffff', broadcast=None)]}
{'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')]}
>>>

*New in 2.2.0*
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
Expand Down Expand Up @@ -1291,10 +1300,6 @@ Constants
.. data:: AF_LINK

Constant which identifies a MAC address associated with a network interface.
Starting from Python 3.4 this is also available as
`socket.AF_LINK <https://docs.python.org/3/library/socket.html#socket.AF_LINK>`__.
To be used in conjunction with :func:`psutil.net_if_addrs()`.

Availability: OSX, FreeBSD

*New in 2.2.0*
*New in 3.0.0*
3 changes: 1 addition & 2 deletions examples/ifconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@


af_map = {
getattr(socket, "AF_PACKET", 17): 'HWADDR',
getattr(psutil, "AF_LINK", -1): 'HWADDR', # OSX / BSD only
socket.AF_INET: 'IPv4',
socket.AF_INET6: 'IPv6',
psutil.AF_LINK: 'HWADDR',
}


Expand Down
19 changes: 14 additions & 5 deletions psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"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
Expand Down Expand Up @@ -160,9 +161,11 @@
else:
raise NotImplementedError('platform %s is not supported' % sys.platform)

# extend the local __all__ context
__all__.extend(_psplatform.__extra__all__)


AF_LINK = _psplatform.AF_LINK
_TOTAL_PHYMEM = None
_POSIX = os.name == 'posix'
_WINDOWS = os.name == 'nt'
Expand Down Expand Up @@ -1818,16 +1821,22 @@ def net_connections(kind='inet'):


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:
"""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 (one of the socket.AF_* constant)
- family
- address
- netmask
- broadcast
address, netmask and broadcast may be None in case NIC address is
not assigned.
'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:
Expand Down
2 changes: 2 additions & 0 deletions psutil/_psbsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import errno
import functools
import os
import socket
import sys
from collections import namedtuple

Expand Down Expand Up @@ -49,6 +50,7 @@
}

PAGESIZE = os.sysconf("SC_PAGE_SIZE")
AF_LINK = socket.AF_LINK

# extend base mem ntuple with BSD-specific memory metrics
svmem = namedtuple(
Expand Down
1 change: 1 addition & 0 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
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
Expand Down
2 changes: 2 additions & 0 deletions psutil/_psosx.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import errno
import functools
import os
import socket
from collections import namedtuple

from psutil import _common
Expand All @@ -23,6 +24,7 @@
# --- constants

PAGESIZE = os.sysconf("SC_PAGE_SIZE")
AF_LINK = socket.AF_LINK

# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h
TCP_STATUSES = {
Expand Down
1 change: 1 addition & 0 deletions psutil/_pssunos.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
__extra__all__ = ["CONN_IDLE", "CONN_BOUND"]

PAGE_SIZE = os.sysconf('SC_PAGE_SIZE')
AF_LINK = socket.AF_LINK

CONN_IDLE = "IDLE"
CONN_BOUND = "BOUND"
Expand Down
1 change: 1 addition & 0 deletions psutil/_pswindows.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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,
Expand Down
2 changes: 1 addition & 1 deletion test/_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def test_cpu_times(self):
def test_net_if_addrs_ips(self):
for name, addrs in psutil.net_if_addrs().items():
for addr in addrs:
if addr.family == socket.AF_PACKET:
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))
Expand Down
26 changes: 17 additions & 9 deletions test/test_psutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,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)

Expand Down Expand Up @@ -1039,10 +1041,7 @@ def test_net_if_addrs(self):
# 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 hasattr(psutil, "AF_LINK"):
families.append(psutil.AF_LINK)
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:
Expand All @@ -1065,11 +1064,20 @@ def test_net_if_addrs(self):
s = socket.socket(af, socktype, proto)
with contextlib.closing(s):
s.bind(sa)

if BSD or OSX:
psutil.AF_LINK
if hasattr(socket, "AF_LINK"): # python >= 3.4
self.assertEqual(psutil.AF_LINK, socket.AF_LINK)
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:
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')
Expand Down

0 comments on commit 80b04a5

Please sign in to comment.