Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add in support for network interface flags. #2037

Merged
merged 4 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -789,3 +789,7 @@ I: 2099

N: Torsten Blum
I: 2114

N: Chris Lalancette
W: https://github.com/clalancette
I: 2037
1 change: 1 addition & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

- 1053_: drop Python 2.6 support. (patches by Matthieu Darbois and Hugo van
Kemenade)
- 2037_: Add additional flags to net_if_stats.
- 2050_, [Linux]: increase ``read(2)`` buffer size from 1k to 32k when reading
``/proc`` pseudo files line by line. This should help having more consistent
results.
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,8 @@ Network
snicaddr(family=<AddressFamily.AF_LINK: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)]}
>>>
>>> psutil.net_if_stats()
{'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536),
'wlan0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500)}
{'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536, flags='up,loopback,running'),
'wlan0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500, flags='up,broadcast,running,multicast')}
>>>

Sensors
Expand Down
12 changes: 10 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -735,20 +735,28 @@ Network
- **speed**: the NIC speed expressed in mega bits (MB), if it can't be
determined (e.g. 'localhost') it will be set to ``0``.
- **mtu**: NIC's maximum transmission unit expressed in bytes.
- **flags**: a string of comma-separated flags on the interface (may be the empty string).
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"may be AN empty string"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d83c959.

Possible flags are: ``up``, ``broadcast``, ``debug``, ``loopback``,
``pointopoint``, ``notrailers``, ``running``, ``noarp``, ``promisc``,
``allmulti``, ``master``, ``slave``, ``multicast``, ``portsel``,
``dynamic``, ``oactive``, ``simplex``, ``link0``, ``link1``, ``link2``,
and ``d2`` (some flags are only available on certain platforms).
Copy link
Owner

@giampaolo giampaolo Sep 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add . Availability: POSIX. After merge I'll try to take a look on how feasible it is to do this on Windows.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added it in 3082526 . Let me know if that is where you want it.


Example:

>>> import psutil
>>> psutil.net_if_stats()
{'eth0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500),
'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536)}
{'eth0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500, flags='up,broadcast,running,multicast'),
'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536, flags='up,loopback,running')}

Also see `nettop.py`_ and `ifconfig.py`_ for an example application.

.. versionadded:: 3.0.0

.. versionchanged:: 5.7.3 `isup` on UNIX also checks whether the NIC is running.

.. versionchanged:: 5.9.1 *flags* field was added on POSIX.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5.9.2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d83c959.


Sensors
-------

Expand Down
2 changes: 1 addition & 1 deletion psutil/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ class BatteryTime(enum.IntEnum):
snicaddr = namedtuple('snicaddr',
['family', 'address', 'netmask', 'broadcast', 'ptp'])
# psutil.net_if_stats()
snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu'])
snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu', 'flags'])
# psutil.cpu_stats()
scpustats = namedtuple(
'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'])
Expand Down
7 changes: 5 additions & 2 deletions psutil/_psaix.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ def net_if_stats():
names = set([x[0] for x in net_if_addrs()])
ret = {}
for name in names:
isup, mtu = cext.net_if_stats(name)
mtu = cext_posix.net_if_mtu(name)
flags = cext_posix.net_if_flags(name)

# try to get speed and duplex
# TODO: rewrite this in C (entstat forks, so use truss -f to follow.
Expand All @@ -257,8 +258,10 @@ def net_if_stats():
speed = int(re_result.group(1))
duplex = re_result.group(2)

output_flags = ','.join(flags)
isup = 'running' in flags
duplex = duplex_map.get(duplex, NIC_DUPLEX_UNKNOWN)
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
ret[name] = _common.snicstats(isup, duplex, speed, mtu, output_flags)
return ret


Expand Down
6 changes: 4 additions & 2 deletions psutil/_psbsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ def net_if_stats():
for name in names:
try:
mtu = cext_posix.net_if_mtu(name)
isup = cext_posix.net_if_is_running(name)
flags = cext_posix.net_if_flags(name)
duplex, speed = cext_posix.net_if_duplex_speed(name)
except OSError as err:
# https://github.com/giampaolo/psutil/issues/1279
Expand All @@ -390,7 +390,9 @@ def net_if_stats():
else:
if hasattr(_common, 'NicDuplex'):
duplex = _common.NicDuplex(duplex)
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
output_flags = ','.join(flags)
isup = 'running' in flags
ret[name] = _common.snicstats(isup, duplex, speed, mtu, output_flags)
return ret


Expand Down
6 changes: 4 additions & 2 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -1060,7 +1060,7 @@ def net_if_stats():
for name in names:
try:
mtu = cext_posix.net_if_mtu(name)
isup = cext_posix.net_if_is_running(name)
flags = cext_posix.net_if_flags(name)
duplex, speed = cext.net_if_duplex_speed(name)
except OSError as err:
# https://github.com/giampaolo/psutil/issues/1279
Expand All @@ -1069,7 +1069,9 @@ def net_if_stats():
else:
debug(err)
else:
ret[name] = _common.snicstats(isup, duplex_map[duplex], speed, mtu)
output_flags = ','.join(flags)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rename this variable to just flags

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here.

isup = 'running' in flags
ret[name] = _common.snicstats(isup, duplex_map[duplex], speed, mtu, output_flags)
return ret


Expand Down
6 changes: 4 additions & 2 deletions psutil/_psosx.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def net_if_stats():
for name in names:
try:
mtu = cext_posix.net_if_mtu(name)
isup = cext_posix.net_if_is_running(name)
flags = cext_posix.net_if_flags(name)
duplex, speed = cext_posix.net_if_duplex_speed(name)
except OSError as err:
# https://github.com/giampaolo/psutil/issues/1279
Expand All @@ -272,7 +272,9 @@ def net_if_stats():
else:
if hasattr(_common, 'NicDuplex'):
duplex = _common.NicDuplex(duplex)
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
output_flags = ','.join(flags)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rename this variable to just flags

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here.

isup = 'running' in flags
ret[name] = _common.snicstats(isup, duplex, speed, mtu, output_flags)
return ret


Expand Down
2 changes: 1 addition & 1 deletion psutil/_pssunos.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def net_if_stats():
isup, duplex, speed, mtu = items
if hasattr(_common, 'NicDuplex'):
duplex = _common.NicDuplex(duplex)
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
ret[name] = _common.snicstats(isup, duplex, speed, mtu, '')
return ret


Expand Down
182 changes: 182 additions & 0 deletions psutil/_psutil_posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <Python.h>
#include <errno.h>
#include <stdlib.h>
#include <stdbool.h>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can remove this, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, removed in 3082526

#include <sys/resource.h>
#include <sys/types.h>
#include <signal.h>
Expand Down Expand Up @@ -429,6 +430,186 @@ psutil_net_if_mtu(PyObject *self, PyObject *args) {
return PyErr_SetFromErrno(PyExc_OSError);
}

static bool
check_and_append_iff_flag(PyObject *py_retlist, short int flags, short int flag_to_check, const char * flag_name)
{
PyObject *py_str = NULL;

if (flags & flag_to_check) {
py_str = PyUnicode_DecodeFSDefault(flag_name);
if (! py_str)
return false;
if (PyList_Append(py_retlist, py_str)) {
Py_DECREF(py_str);
return false;
}
Py_CLEAR(py_str);
}

return true;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like doing the flag comparison / check in here (if flags & flag_to_check ...). That should happen in the main function. The utility function (this one) should only add the item to the dict.

Also, instead of return true or false I would just return 1 or 0_, which is consistent with the rest of the code base and avoids including <stdbool.h> (I have some memories of this header file causing problems on some platform).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. I've switched this helper function to return an int, and I'm now doing the check in the main function. All in d83c959.


/*
* Get all of the NIC flags and return them.
*/
static PyObject *
psutil_net_if_flags(PyObject *self, PyObject *args) {
char *nic_name;
int sock = -1;
int ret;
struct ifreq ifr;
PyObject *py_retlist = PyList_New(0);
short int flags;

if (py_retlist == NULL)
return NULL;

if (! PyArg_ParseTuple(args, "s", &nic_name))
goto error;

sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1) {
PyErr_SetFromErrno(PyExc_OSError);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use PyErr_SetFromOSErrnoWithSyscall("socket(SOCK_DGRAM)");.
Since in this function we can fail for 2 different reasons, this will tell where the error originated from (socket() instead of ioctl()).

(sorry, I know I told you to PyErr_SetFromErrno use this but I forgot we have PyErr_SetFromOSErrnoWithSyscall)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, it happens. Fixed in d83c959.

goto error;
}

PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
ret = ioctl(sock, SIOCGIFFLAGS, &ifr);
if (ret == -1) {
PyErr_SetFromErrno(PyExc_OSError);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCGIFFLAGS)");.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d83c959.

goto error;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add PyErr_SetFromErrno(PyExc_OSError);, then goto error;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e05f926.

}

close(sock);
sock = -1;

flags = ifr.ifr_flags & 0xFFFF;

// Linux/glibc IFF flags: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/gnu/net/if.h;h=251418f82331c0426e58707fe4473d454893b132;hb=HEAD
// macOS IFF flags: https://opensource.apple.com/source/xnu/xnu-792/bsd/net/if.h.auto.html
// AIX IFF flags: https://www.ibm.com/support/pages/how-hexadecimal-flags-displayed-ifconfig-are-calculated
// FreeBSD IFF flags: https://www.freebsd.org/cgi/man.cgi?query=if_allmulti&apropos=0&sektion=0&manpath=FreeBSD+10-current&format=html
clalancette marked this conversation as resolved.
Show resolved Hide resolved

#ifdef IFF_UP
// Available in (at least) Linux, macOS, AIX, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_UP, "up"))
goto error;
#endif
#ifdef IFF_BROADCAST
// Available in (at least) Linux, macOS, AIX, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_BROADCAST, "broadcast"))
goto error;
#endif
#ifdef IFF_DEBUG
// Available in (at least) Linux, macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_DEBUG, "debug"))
goto error;
#endif
#ifdef IFF_LOOPBACK
// Available in (at least) Linux, macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_LOOPBACK, "loopback"))
goto error;
#endif
#ifdef IFF_POINTOPOINT
// Available in (at least) Linux, macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_POINTOPOINT, "pointopoint"))
goto error;
#endif
#ifdef IFF_NOTRAILERS
// Available in (at least) Linux, macOS, AIX
if (!check_and_append_iff_flag(py_retlist, flags, IFF_NOTRAILERS, "notrailers"))
goto error;
#endif
#ifdef IFF_RUNNING
// Available in (at least) Linux, macOS, AIX, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_RUNNING, "running"))
goto error;
#endif
#ifdef IFF_NOARP
// Available in (at least) Linux, macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_NOARP, "noarp"))
goto error;
#endif
#ifdef IFF_PROMISC
// Available in (at least) Linux, macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_PROMISC, "promisc"))
goto error;
#endif
#ifdef IFF_ALLMULTI
// Available in (at least) Linux, macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_ALLMULTI, "allmulti"))
goto error;
#endif
#ifdef IFF_MASTER
// Available in (at least) Linux
if (!check_and_append_iff_flag(py_retlist, flags, IFF_MASTER, "master"))
goto error;
#endif
#ifdef IFF_SLAVE
// Available in (at least) Linux
if (!check_and_append_iff_flag(py_retlist, flags, IFF_SLAVE, "slave"))
goto error;
#endif
#ifdef IFF_MULTICAST
// Available in (at least) Linux, macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_MULTICAST, "multicast"))
goto error;
#endif
#ifdef IFF_PORTSEL
// Available in (at least) Linux
if (!check_and_append_iff_flag(py_retlist, flags, IFF_PORTSEL, "portsel"))
goto error;
#endif
#ifdef IFF_AUTOMEDIA
// Available in (at least) Linux
if (!check_and_append_iff_flag(py_retlist, flags, IFF_AUTOMEDIA, "automedia"))
goto error;
#endif
#ifdef IFF_DYNAMIC
// Available in (at least) Linux
if (!check_and_append_iff_flag(py_retlist, flags, IFF_DYNAMIC, "dynamic"))
goto error;
#endif
#ifdef IFF_OACTIVE
// Available in (at least) macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_OACTIVE, "oactive"))
goto error;
#endif
#ifdef IFF_SIMPLEX
// Available in (at least) macOS, AIX, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_SIMPLEX, "simplex"))
goto error;
#endif
#ifdef IFF_LINK0
// Available in (at least) macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_LINK0, "link0"))
goto error;
#endif
#ifdef IFF_LINK1
// Available in (at least) macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_LINK1, "link1"))
goto error;
#endif
#ifdef IFF_LINK2
// Available in (at least) macOS, BSD
if (!check_and_append_iff_flag(py_retlist, flags, IFF_LINK2, "link2"))
goto error;
#endif
#ifdef IFF_D2
// Available in (at least) AIX
if (!check_and_append_iff_flag(py_retlist, flags, IFF_D2, "d2"))
goto error;
#endif

return py_retlist;

error:
Py_DECREF(py_retlist);
if (sock != -1)
close(sock);
return NULL;
}


/*
* Inspect NIC flags, returns a bool indicating whether the NIC is
Expand Down Expand Up @@ -667,6 +848,7 @@ static PyMethodDef mod_methods[] = {
{"getpagesize", psutil_getpagesize_pywrapper, METH_VARARGS},
{"getpriority", psutil_posix_getpriority, METH_VARARGS},
{"net_if_addrs", psutil_net_if_addrs, METH_VARARGS},
{"net_if_flags", psutil_net_if_flags, METH_VARARGS},
{"net_if_is_running", psutil_net_if_is_running, METH_VARARGS},
{"net_if_mtu", psutil_net_if_mtu, METH_VARARGS},
{"setpriority", psutil_posix_setpriority, METH_VARARGS},
Expand Down
2 changes: 1 addition & 1 deletion psutil/_pswindows.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ def net_if_stats():
isup, duplex, speed, mtu = items
if hasattr(_common, 'NicDuplex'):
duplex = _common.NicDuplex(duplex)
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
ret[name] = _common.snicstats(isup, duplex, speed, mtu, '')
return ret


Expand Down
2 changes: 1 addition & 1 deletion psutil/tests/test_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@ def test_net_if_stats(self):
psutil.NIC_DUPLEX_UNKNOWN)
for name, stats in nics.items():
self.assertIsInstance(name, str)
isup, duplex, speed, mtu = stats
isup, duplex, speed, mtu, flags = stats
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add:

self.assertIsInstance(flags, str)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d83c959.

self.assertIsInstance(isup, bool)
self.assertIn(duplex, all_duplexes)
self.assertIn(duplex, all_duplexes)
Expand Down