python
March 1

Получение IP -, MAC –адресов, имени сетевого интерфейса с помощью Python

Порою возникают ситуации, когда необходимо использовать в скрипте Python IP-адрес используемый активной сетевой картой, которая смотрит в Интернет, узнать MAC-адрес этой карты и имя сетевого соединения. К сожалению, функций из коробки пока что не наблюдается. Есть сторонние модули, которые позволяют узнать MAC-адрес, например getmac, но в качестве параметров в них нужно также передавать или IP-адрес, или имя соединения. Но, что, если их нужно определять программно и вводить вручную не вариант?
Я нашел для себя решение, которое работает, но требует тестирования на большом количестве систем. Хотя, думаю, что на большинстве ОС семейства Windows или Linux оно будет работать.

Импорт библиотек

В данном решении не требуется устанавливать сторонние библиотеки. Необходимо лишь импортировать в скрип те, что нужны для его работы. Выполним их импорт:

from platform import system
from re import sub
from subprocess import check_output
from socket import socket, AF_INET, SOCK_DGRAM

Получение IP-адреса активной сетевой карты

Уже очень давно, около 15 лет назад на Stack Overflow был дан ответ по поводу получения «основного», имеющего маршрут по умолчанию, IP-адреса. Как описывает его автор скрипта, он работает под всеми основными ОС: Windows, Linux, OSX. Вот ссылка на данный пост.
Несмотря на то, что скрипту уже довольно много лет, он работает до сих пор. Потому, вместо изобретения велосипеда я использую его, за неимением лучшего. Тем более что он ни разу не давал сбоев. Вот сам скрипт:

def local():
    st = socket(AF_INET, SOCK_DGRAM)
    try:
        st.connect(('10.255.255.255', 1))
        ip = st.getsockname()[0]
    except Exception:
        ip = '127.0.0.1'
    finally:
        st.close()
    return ip

Теперь, когда мы определились с тем, каким способом будем получать IP-адрес, приступим к написанию скрипта для получения IPv6-, MAC- адресов, а также имени сетевого интерфейса.

Получение информации об активном сетевом интерфейсе

Создадим класс NetInfo,который при инициализации будет получать необходимые данные в зависимости от операционной системы. Здесь мы определяем платформу с помощью модуля system библиотеки platform и в зависимости от этого запускаем тот или иной скрипт.

class NetInfo:
    def __init__(self):
        self.ipv4 = self.local()
        self.platform = system()
        self.mac = None
        self.iface = None
        self.ipv6 = None
        if self.platform == "Windows":
            self.mac_iface_win()
        elif self.platform == "Linux":
            self.mac_iface_lin()
        else:
            exit(0)
        else:
            exit(0)

Двигаемся дальше. Создадим функцию для получения локального IP-адерса и объявим ее статическим методом, так как в ней не используются переменные класса.

@staticmethod
def local():
    st = socket(AF_INET, SOCK_DGRAM)
    try:
        st.connect(('10.255.255.255', 1))
        ip = st.getsockname()[0]
    except OSError:
        ip = '127.0.0.1'
    finally:
        st.close()
    return ip

Напишем функцию mac_iface_win(self) в которой выполним получение нужных нам параметров. Для этого мы будем использовать возможности командной строки Windows, с частности инструмент командной стоки wmic. В частности будем использовать псевдоним NICCONFIG, который используется для управления сетевыми адаптерами. Отфильтруем только активные сетевые адаптеры. А их может быть в системе несколько, включая виртуальные: IPEnabled=true . После этого получим MAC – и IP – адреса используя GET MACAddress, IPAddress /FORMAT:csv, с указанием вывода полученных значений в формате csv для того, чтобы нам было проще их распарсить.

Вот полный вид данной команды:

wmic NICCONFIG WHERE IPEnabled=true GET MACAddress, IPAddress /FORMAT:csv

После того, как мы получим список активных сетевых адаптеров, поитерируемся по нему в цикле и проверим, есть ли в данном списке адаптер, IP-адрес которого равен полученному ранее локальному адресу. Если есть, забираем MAC-адрес, а также IPv6 адрес, если он не отключен в системе.

После того, как мы получим необходимые данные, выполним команду getmac /FO csv /NH /V с помощью которой получим список сетевых интерфейсов. Также в цикле проитерируемся по нему и будем проверять, есть ли уже полученный MAC-адрес в строке с параметрами интерфейса. Если есть, забираем название сетевого интерфейса.

def mac_iface_win(self):
    adapter_lst = check_output("wmic NICCONFIG WHERE IPEnabled=true GET MACAddress, "
                               "IPAddress /FORMAT:csv", shell=False).decode().strip().splitlines()
    for adapter in adapter_lst:
        if adapter.strip():
            node, ipaddr, mac = adapter.split(",")
            ipaddr = sub("[{}]", "", ipaddr).split(";")
            if ipaddr[0] == self.ipv4:
                self.mac = mac.upper()
                try:
                    self.ipv6 = ipaddr[1]
                except IndexError:
                    pass
    if self.mac:
        interface_all = check_output('getmac /FO csv /NH /V', shell=False).decode('cp866').splitlines()
        for line in interface_all:
            if self.mac.upper().replace(":", "-") in line:
                self.iface = line.split(",")[0].replace('"', '')
                break

Если в предыдущей функции мы получили данные для сетевого интерфейса в ОС Windows, то следует также написать аналогичную функцию и для Linux. Поэтому создадим функцию mac_iface_lin(self). В ней кода будет поменьше, так как в командах Linux содержится больше информации в одном месте и ее легче распарсить. Команда, которую мы будем использовать выглядит следующим образом:

ip -h -br a | grep UP

Здесь мы получим название сетевого интерфейса и IPv6-адрес. В Linux данный адрес можно получить, даже если он отключен в настройках адаптера.
После этого выполним похожую команду, но уже отфильтруем из ее вывода MAC-адрес:

ip a | grep ether | gawk '{print $2}'

Осталось только свести написанный код, если вы этого еще не сделали воедино.

def mac_iface_lin(self):
    com_run = check_output('ip -h -br a | grep UP', shell=True).decode().split()
    self.iface = com_run[0].strip()
    self.ipv6 = com_run[3].strip().split("/")[0]
    self.mac = check_output("ip a | grep ether | gawk '{print $2}'", shell=True).decode().strip().upper()

Итак, продолжим. Полный код скрипта выглядит следующим образом:

from platform import system
from re import sub
from subprocess import check_output
from socket import socket, AF_INET, SOCK_DGRAM


class NetInfo:
    def __init__(self):
        self.ipv4 = self.local()
        self.platform = system()
        self.mac = None
        self.iface = None
        self.ipv6 = None
        if self.platform == "Windows":
            self.mac_iface_win()
        elif self.platform == "Linux":
            self.mac_iface_lin()
        else:
            exit(0)

    @staticmethod
    def local():
        st = socket(AF_INET, SOCK_DGRAM)
        try:
            st.connect(('10.255.255.255', 1))
            ip = st.getsockname()[0]
        except OSError:
            ip = '127.0.0.1'
        finally:
            st.close()
        return ip

    def mac_iface_win(self):
        adapter_lst = check_output("wmic NICCONFIG WHERE IPEnabled=true GET MACAddress, "
                                   "IPAddress /FORMAT:csv", shell=False).decode().strip().splitlines()
        for adapter in adapter_lst:
            if adapter.strip():
                node, ipaddr, mac = adapter.split(",")
                ipaddr = sub("[{}]", "", ipaddr).split(";")
                if ipaddr[0] == self.ipv4:
                    self.mac = mac.upper()
                    try:
                        self.ipv6 = ipaddr[1]
                    except IndexError:
                        pass
        if self.mac:
            interface_all = check_output('getmac /FO csv /NH /V', shell=False).decode('cp866').splitlines()
            for line in interface_all:
                if self.mac.upper().replace(":", "-") in line:
                    self.iface = line.split(",")[0].replace('"', '')
                    break

    def mac_iface_lin(self):
        com_run = check_output('ip -h -br a | grep UP', shell=True).decode().split()
        self.iface = com_run[0].strip()
        self.ipv6 = com_run[3].strip().split("/")[0]
        self.mac = check_output("ip a | grep ether | gawk '{print $2}'", shell=True).decode().strip().upper()

Теперь необходимо протестировать его в операционных системах. В данном случае у меня доступны две системы: Windows 10 и Linux Mint.

Создадим в данном скрипте вызов нашего класса и выведем в терминал полученные параметры:

name = NetInfo().iface
ip_ = NetInfo().ipv4
ip_6 = NetInfo().ipv6
mac_ = NetInfo().mac
print(f'{name} | {ip_} | {ip_6} | {mac_}')

Для начала, запустим в ОС Windows:

А теперь то же самое в Linux Mint:

Как видим, скрипт справляется со своей работой. Для чего он может пригодиться? Ну, например, для автоматической установки активного сетевого интерфейса по умолчанию в Scapy при прослушивании пакетов с активного сетевого интерфейса. Но о Scapy поговорим немного позже.

Спасибо за внимание. Надеюсь, данная информация будет вам полезна

Наши тг каналы, подпишись!