Сообщить об ошибке.

Информация о сетевых картах и их соединениях, модуль psutil в Python

Статистика сетевых соединений и сетевого ввода-вывода в Python

Материал содержит описание функций модуля psutil с примерами, которые возвращают информацию о сетевых картах и их соединениях, такую как: статистику сетевого ввода-вывода, соединения сокетов, адреса сетевых карт.

Содержание:


psutil.net_io_counters(pernic=False, nowrap=True):

Функция psutil.net_io_counters() возвращает общесистемную статистику сетевого ввода-вывода в виде именованного кортежа, включающего следующие атрибуты:

  • bytes_sent: количество отправленных байтов;
  • bytes_recv: количество полученных байтов;
  • packets_sent: количество отправленных пакетов;
  • packets_recv: количество полученных пакетов;
  • errin: общее количество ошибок при получении;
  • errout: общее количество ошибок при отправке;
  • dropin: общее количество входящих пакетов, которые были отброшены;
  • dropout: общее количество исходящих пакетов, которые были отброшены (всегда 0 на macOS и BSD).

Если pernic равно True, то возвращает ту же информацию для каждого сетевого интерфейса, установленного в системе, в виде словаря с именами сетевых интерфейсов в качестве ключей и именованным кортежем, в качестве значений описанных выше.

Пример использования psutil.net_io_counters():

>>> import psutil
>>> psutil.net_io_counters()
# snetio(bytes_sent=14508483, bytes_recv=62749361, packets_sent=84311, 
#        packets_recv=94888, errin=0, errout=0, dropin=0, dropout=0)
>>> psutil.net_io_counters(pernic=True)
# {
#   'lo': snetio(bytes_sent=547971, bytes_recv=547971, packets_sent=5075, 
#                packets_recv=5075, errin=0, errout=0, dropin=0, dropout=0),
#   'wlan0': snetio(bytes_sent=13921765, bytes_recv=62162574, packets_sent=79097, 
#                   packets_recv=89648, errin=0, errout=0, dropin=0, dropout=0)
#}

В некоторых системах, таких как Linux, в очень загруженных или долгоживущих системах числа, возвращаемые ядром, могут переполняться и переноситься (перезапускаться с нуля). Если nowrap имеет значение True, то модуль psutil обнаружит и скорректирует эти числа при вызовах функций и добавит "старое значение" к "новому значению", чтобы возвращаемые числа всегда увеличивались или оставались неизменными, но никогда не уменьшались. Для аннулирования кеша nowrap можно использовать метод psutil.net_io_counters.cache_clear(). На машинах без сетевых интерфейсов эта функция вернет None или {}, если pernic имеет значение True.

psutil.net_connections(kind='inet'):

Функция psutil.net_connections() возвращает общесистемные соединения сокетов в виде списка именованных кортежей. Каждый именованный кортеж предоставляет 7 атрибутов:

  • fd: дескриптор файла сокета. Если соединение относится к текущему процессу, это может быть передано socket.fromfd() для получения пригодного для использования объекта сокета. В Windows и SunOS это значение всегда равно -1.
  • family: семейство адресов, либо AF_INET, AF_INET6, либо AF_UNIX.
  • type: тип адреса: SOCK_STREAM, SOCK_DGRAM или SOCK_SEQPACKET.
  • laddr: локальный адрес в виде (ip, port), именованный кортеж или путь path в случае сокетов AF_UNIX. Для сокетов UNIX смотрите примечания ниже.
  • raddr: удаленный адрес в виде (ip, port), именованный кортеж или абсолютный путь path в случае сокетов UNIX. Когда удаленная конечная точка не подключена, то возвращается пустой кортеж (AF_INET*) или '' (AF_UNIX). Для сокетов UNIX смотрите примечания ниже.
  • status: представляет состояние TCP-соединения. Возвращаемое значение является одной из констант psutil.CONN_* (строка). Для сокетов UDP и UNIX это всегда будет psutil.CONN_NONE.
  • pid: PID процесса, открывшего сокет, если его можно получить, иначе None. На некоторых платформах (например, Linux) доступность этого поля меняется в зависимости от привилегий процесса (необходим root доступ).

Аргумент kind представляет собой строку, которая фильтрует соединения, соответствующие следующим критериям.

Аргумент kindСоединения с использованием
"inet"IPv4 и IPv6
"inet4"IPv4
"inet6"IPv6
"tcp"TCP
"tcp4"TCP поверх IPv4
"tcp6"TCP поверх IPv6
"udp"UDP
"udp4"UDP поверх IPv4
"udp6"UDP поверх IPv6
"unix"Сокет UNIX (как протоколы UDP, так и TCP)
"all"сумма всех возможных семейств и протоколов

В macOS и AIX для этой функции требуются привилегии root. Чтобы получить соединения для каждого процесса, необходимо использовать объект Process.connections().

Пример использования psutil.net_connections():

>>> import psutil
>>> psutil.net_connections()
# [
#  pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, 
#        laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), 
#        status='ESTABLISHED', pid=1254),
#  pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, 
#        laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), 
#        status='CLOSING', pid=2987),
#  pconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, 
#        laddr=addr(ip='10.0.0.1', port=60759), raddr=addr(ip='72.14.234.104', port=80), 
#        status='ESTABLISHED', pid=None),
#  pconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, 
#        laddr=addr(ip='10.0.0.1', port=51314), raddr=addr(ip='72.14.234.83', port=443), 
#        status='SYN_SENT', pid=None)
#  ...]

Примечание:

  • (macOS и AIX) всегда вызывается psutil.AccessDenied, если только он не запущен от имени пользователя root. Это ограничение ОС, и утилита lsof делает то же самое.
  • (Solaris) Сокеты UNIX не поддерживаются.
  • (Linux, FreeBSD) Поле raddr для сокетов UNIX всегда имеет значение ''. Это ограничение операционной системы.
  • (OpenBSD) Поля laddr и raddr для сокетов UNIX всегда имеют значение ''. Это ограничение операционной системы.

psutil.net_if_addrs():

Функция psutil.net_if_addrs() возвращает адреса, связанные с каждой сетевой картой, установленной в системе, в виде словаря, ключами которого являются имена сетевых карт, а значением - список именованных кортежей для каждого адреса, назначенного сетевой карте. Каждый именованный кортеж включает в себя 5 полей:

  • family: семейство адресов, либо AF_INET, либо AF_INET6, либо psutil.AF_LINK, который ссылается на MAC-адрес.
  • address: адрес основной сетевой карты (устанавливается всегда).
  • netmask: адрес сетевой маски (может отсутствовать).
  • broadcast: широковещательный адрес (может быть None).
  • ptp: расшифровывается как "точка-точка". Это адрес назначения в интерфейсе (обычно VPN). Широковещательная передача и ptp являются взаимоисключающими. Может быть или нет.

Пример использования psutil.net_if_addrs():

>>> import psutil
>>> psutil.net_if_addrs()
# {
# 'lo': [snicaddr(family=<AddressFamily.AF_INET: 2>, address='127.0.0.1', netmask='255.0.0.0', 
#        broadcast='127.0.0.1', ptp=None), snicaddr(family=<AddressFamily.AF_INET6: 10>, 
#        address='::1', netmask='ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', broadcast=None,
#        ptp=None), snicaddr(family=<AddressFamily.AF_LINK: 17>, 
#        address='00:00:00:00:00:00', netmask=None, broadcast='00:00:00:00:00:00', ptp=None)],
# 'wlan0': [snicaddr(family=<AddressFamily.AF_INET: 2>, address='192.168.1.3', netmask='255.255.255.0', 
#           broadcast='192.168.1.255', ptp=None), snicaddr(family=<AddressFamily.AF_INET6: 10>, 
#           address='fe80::c685:8ff:fe45:641%wlan0', netmask='ffff:ffff:ffff:ffff::', broadcast=None,
#           ptp=None), snicaddr(family=<AddressFamily.AF_LINK: 17> , 
#           address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)]
# }

Примечания:

  • если интересуют другие семейства адресов (например, AF_BLUETOOTH), то можно использовать более мощный сторонний модуль netifaces.
  • можно иметь более одного адреса одного и того же семейства, связанного с каждым интерфейсом (поэтому значения dict представляют собой списки).
  • широковещательная передача и ptp не поддерживаются в Windows и всегда имеют значение None.

psutil.net_if_stats():

Функция psutil.net_if_stats() возвращает информацию о каждой сетевой карте (интерфейсной плате), установленной в системе, в виде словаря, ключами которого являются имена сетевых карт, а значением - именованный кортеж со следующими полями:

  • isup: логическое значение, указывающее, запущена ли сетевая карта (имеется в виду подключение кабеля Ethernet или Wi-Fi).
  • duplex: дуплексный тип связи; это может быть psutil.NIC_DUPLEX_FULL, psutil.NIC_DUPLEX_HALF или psutil.NIC_DUPLEX_UNKNOWN.
  • speed: скорость сетевого адаптера, выраженная в мегабитах (МБ), если ее невозможно определить (например, ‘localhost’), то будет установлена в 0.
  • mtu: максимальная единица передачи сетевого адаптера, выраженная в байтах..
  • flags: флаги интерфейса - строка разделенных запятыми (может быть пустой строкой). Возможные флаги: up, broadcast, debug, loopback, pointopoint, notrailers, running, noarp, promisc, allmulti, master, slave, multicast, portsel, dynamic, oactive, simplex, link0, link1, link2, и d2 (некоторые флаги доступны только на определенных платформах).

Доступность: UNIX

Пример использования psutil.net_if_stats():

>>> import psutil
>>> psutil.net_if_stats()
# {
#   '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')
# }

Пример вывода информации по сетевым картам.

Этот пример представляет собой клон Linux утилиты ifconfig:

import socket
import psutil
from psutil._common import bytes2human

af_map = {
    socket.AF_INET: 'IPv4',
    socket.AF_INET6: 'IPv6',
    psutil.AF_LINK: 'MAC',
}

duplex_map = {
    psutil.NIC_DUPLEX_FULL: "full",
    psutil.NIC_DUPLEX_HALF: "half",
    psutil.NIC_DUPLEX_UNKNOWN: "?",
}

def main():
    stats = psutil.net_if_stats()
    io_counters = psutil.net_io_counters(pernic=True)
    for nic, addrs in psutil.net_if_addrs().items():
        print("%s:" % (nic))
        if nic in stats:
            st = stats[nic]
            print("    stats          : ", end='')
            print("speed=%sMB, duplex=%s, mtu=%s, up=%s" % (
                st.speed, duplex_map[st.duplex], st.mtu,
                "yes" if st.isup else "no"))
        if nic in io_counters:
            io = io_counters[nic]
            print("    incoming       : ", end='')
            print("bytes=%s, pkts=%s, errs=%s, drops=%s" % (
                bytes2human(io.bytes_recv), io.packets_recv, io.errin,
                io.dropin))
            print("    outgoing       : ", end='')
            print("bytes=%s, pkts=%s, errs=%s, drops=%s" % (
                bytes2human(io.bytes_sent), io.packets_sent, io.errout,
                io.dropout))
        for addr in addrs:
            print("    %-4s" % 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)
            if addr.ptp:
                print("      p2p       : %s" % addr.ptp)
        print("")

if __name__ == '__main__':
    main()

Вывод сценария:

$ python3 test.py
lo:
    stats          : speed=0MB, duplex=?, mtu=65536, up=yes
    incoming       : bytes=1.95M, pkts=22158, errs=0, drops=0
    outgoing       : bytes=1.95M, pkts=22158, errs=0, drops=0
    IPv4 address   : 127.0.0.1
         netmask   : 255.0.0.0
    IPv6 address   : ::1
         netmask   : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
    MAC  address   : 00:00:00:00:00:00
docker0:
    stats          : speed=0MB, duplex=?, mtu=1500, up=yes
    incoming       : bytes=3.48M, pkts=65470, errs=0, drops=0
    outgoing       : bytes=164.06M, pkts=112993, errs=0, drops=0
    IPv4 address   : 172.17.0.1
         broadcast : 172.17.0.1
         netmask   : 255.255.0.0
    IPv6 address   : fe80::42:27ff:fe5e:799e%docker0
         netmask   : ffff:ffff:ffff:ffff::
    MAC  address   : 02:42:27:5e:79:9e
         broadcast : ff:ff:ff:ff:ff:ff
wlp3s0:
    stats          : speed=0MB, duplex=?, mtu=1500, up=yes
    incoming       : bytes=7.04G, pkts=5637208, errs=0, drops=0
    outgoing       : bytes=372.01M, pkts=3200026, errs=0, drops=0
    IPv4 address   : 10.0.0.2
         broadcast : 10.255.255.255
         netmask   : 255.0.0.0
    IPv6 address   : fe80::ecb3:1584:5d17:937%wlp3s0
         netmask   : ffff:ffff:ffff:ffff::
    MAC  address   : 48:45:20:59:a4:0c
         broadcast : ff:ff:ff:ff:ff:ff

Пример статистики сетевого ввода-вывода в реальном времени.

import sys
import time

try:
    import curses
except ImportError:
    sys.exit('платформа не поддерживается')

import psutil
from psutil._common import bytes2human

lineno = 0
win = curses.initscr()

def printl(line, highlight=False):
    """Тонкая обертка вокруг модуля curses."""
    global lineno
    try:
        if highlight:
            line += " " * (win.getmaxyx()[1] - len(line))
            win.addstr(lineno, 0, line, curses.A_REVERSE)
        else:
            win.addstr(lineno, 0, line, 0)
    except curses.error:
        lineno = 0
        win.refresh()
        raise
    else:
        lineno += 1

def poll(interval):
    """Необработанная статистика в интервале `interval`."""
    tot_before = psutil.net_io_counters()
    pnic_before = psutil.net_io_counters(pernic=True)
    # спим в течении `interval`
    time.sleep(interval)
    tot_after = psutil.net_io_counters()
    pnic_after = psutil.net_io_counters(pernic=True)
    return (tot_before, tot_after, pnic_before, pnic_after)

def refresh_window(tot_before, tot_after, pnic_before, pnic_after):
    """Вывод статистики на экран."""
    global lineno
    # всего
    printl("total bytes:           sent: %-10s   received: %s" % (
        bytes2human(tot_after.bytes_sent),
        bytes2human(tot_after.bytes_recv))
    )
    printl("total packets:         sent: %-10s   received: %s" % (
        tot_after.packets_sent, tot_after.packets_recv))

    # сведения об интерфейсе для каждой сети: 
    # отсортируем сетевые интерфейсы, первыми 
    # будут те, которые генерировали больше трафика.
    printl("")
    nic_names = list(pnic_after.keys())
    nic_names.sort(key=lambda x: sum(pnic_after[x]), reverse=True)
    for name in nic_names:
        stats_before = pnic_before[name]
        stats_after = pnic_after[name]
        templ = "%-15s %15s %15s"
        printl(templ % (name, "TOTAL", "PER-SEC"), highlight=True)
        printl(templ % (
            "bytes-sent",
            bytes2human(stats_after.bytes_sent),
            bytes2human(
                stats_after.bytes_sent - stats_before.bytes_sent) + '/s',
        ))
        printl(templ % (
            "bytes-recv",
            bytes2human(stats_after.bytes_recv),
            bytes2human(
                stats_after.bytes_recv - stats_before.bytes_recv) + '/s',
        ))
        printl(templ % (
            "pkts-sent",
            stats_after.packets_sent,
            stats_after.packets_sent - stats_before.packets_sent,
        ))
        printl(templ % (
            "pkts-recv",
            stats_after.packets_recv,
            stats_after.packets_recv - stats_before.packets_recv,
        ))
        printl("")
    win.refresh()
    lineno = 0

def setup():
    curses.start_color()
    curses.use_default_colors()
    for i in range(0, curses.COLORS):
        curses.init_pair(i + 1, i, -1)
    curses.endwin()
    win.nodelay(1)

def tear_down():
    win.keypad(0)
    curses.nocbreak()
    curses.echo()
    curses.endwin()

if __name__ == '__main__':
    setup()
    try:
        interval = 0
        while True:
            if win.getch() == ord('q'):
                break
            args = poll(interval)
            refresh_window(*args)
            interval = 0.5
    except (KeyboardInterrupt, SystemExit):
        pass
    finally:
        tear_down()

Вывод сценария:

$ python3 test.py
-----------------------------------------------------------
total bytes:           sent: 1.49 G       received: 4.82 G
total packets:         sent: 7338724      received: 8082712
wlan0                     TOTAL         PER-SEC
-----------------------------------------------------------
bytes-sent               1.29 G        0.00 B/s
bytes-recv               3.48 G        0.00 B/s
pkts-sent               7221782               0
pkts-recv               6753724               0
eth1                      TOTAL         PER-SEC
-----------------------------------------------------------
bytes-sent             131.77 M        0.00 B/s
bytes-recv               1.28 G        0.00 B/s
pkts-sent                     0               0
pkts-recv               1214470               0