Статья Сканер локальной сети для получения списка IP и MAC адресов на Python

На данный момент в различных операционных системах существует довольно много самых разнообразных сканеров сети, которые не только составят вам ее карту, но, также покажут, какие сетевые ресурсы доступны на каждой из найденных машин, их сетевые имена. Но, почему бы не сделать свой сканер на Python, который будет составлять карту сети, хоть и без сетевых ресурсов на данном этапе, но, тем не менее, работать он будет довольно быстро и в качестве практики очень даже полезно.

000.jpg


Что понадобиться?

Для того, чтобы создать быстрый сканер сети, с возможностью его дальнейшего расширения, нам понадобиться установить scapy. Это многофункциональная библиотека, которая позволяет отправлять, анализировать и подделывать сетевые пакеты, а также сканировать или атаковать сети. Установка ее достаточно простая. Нужно написать в терминале команду:

pip install --pre scapy[basic]

Однако, это не единственный, но рекомендуемый, вариант установки данной библиотеки. Вот небольшая табличка с командами по установке разных по компоновке пакетов. Для наших целей будет вполне достаточно рекомендуемой установки.

screenshot1.png

Если ваша операционная система отличается от Linux, а в данном случае имеется в виду Windows, то потребуется установить библиотеку захвата пакетов Npcap. Если же вы используете Windows XP, хотя на сегодняшнее время это вряд ли, то нужно использовать библиотеку WinPcap. Установить Npcap можно скачав его с официального сайта.

На этом подготовительный этап можно завершить. Давайте теперь импортируем scapy в наш скрипт.

Python:
from platform import system
from socket import socket, AF_INET, SOCK_DGRAM
from subprocess import check_output
import os

import scapy.all as sc

Как видите, помимо библиотеки scapy для скрипта нужно будет импортировать еще несколько модулей входящих в состав стандартных библиотек python. В данном случае библиотека platform будет нужна для определения операционной системы. Так как данный скрипт будет работать как в Windows, так и в Linux. Библиотека socket используется для получения локального IP-адреса компьютера, а subprocess для выполнения команды и тем самым получения IP-адреса роутера, используемого в системе по умолчанию.


Получаем локальный IP-адрес

Давайте приступим к созданию сканера сети. Для начала нам будет нужна функция, которая получает локальный IP-адрес машины. Создадим ее и назовем, к примеру: local_ipv4(). Данная функция устанавливает соединение с адресом 10.255.255.255, а затем с помощью getsockname возвращает адрес и порт сокета. В данном случае порт нам не нужен, а вот адрес вполне пригодится. Поэтому забираем его из кортежа и возвращаем из функции. Если же произошла ошибка и соединение не удалось, возвращается адрес 127.0.0.1. Что, в принципе, будет соответствовать действительности, так как в данном случае сетевого соединения не будет, а будет использоваться только localhost.

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

Дополнение: функцию получения локального адреса можно сделать немного по другому, но только в том случае, если у вас ОС Linux. Нужно дополнительно установить библиотеки getmac и netifaces.

pip install getmac netifaces

В импорт добавить:

Python:
from getmac.getmac import _get_default_iface_linux
from netifaces import ifaddresses, AF_INET

Тогда функция будет выглядеть вот так:

Python:
def local_ipv4():
    try:
        return ifaddresses(_get_default_iface_linux()).setdefault(AF_INET)[0]['addr']
    except TypeError:
        return '127.0.0.1'


Получение IP-адреса шлюза используемого в системе по умолчанию

На следующем этапе нужно получить IP-адрес шлюза, который используется в системе по умолчанию. Для этого используем subprocess.check_output, которая запускает выполнение определенной команды в терминале и возвращает ее вывод. И для Linux, и для Windows будем использовать похожие команды. Для Linux это «route -n», а вот с Windows все чуточку посложнее, так как там при выполнении команды возвращается куча всяких не особо то нужных в данном случае параметров. Поэтому нужно будет выполнить среди всей этой информации поиск и вывести уже отфильтрованный результат.

Давайте сделаем первую функцию для Windows. Создадим get_gateway_win(). На вход она не получает никаких данных, а на выходе возвращает адрес шлюза. С помощью subprocess.check_output выполняется команда, после чего результат приводится к нужному виду и возвращается из функции.

Python:
def get_gateway_win():
    # получаем адрес шлюза по умолчанию для текущего сетевого интерфейса
    com = f'route PRINT 0* | findstr {local_ipv4()}'.split()
    return check_output(com, shell=True).decode('cp866').split()[2]

И для Linux, создадим функцию get_gateway_linx(). Принцип получения адреса шлюза здесь тот же самый и на выходе получается нужный адрес шлюза.

Python:
def get_gateway_linx():
    com = 'route -n'.split()
    ip_route = str(subprocess.check_output(com, shell=True)).split("\\n")[2].split()[1].strip()
    if ip_route.isdigit():
        return ip_route
    else:
        sock = socket.gethostbyname(ip_route)
        return sock


Сканирование сети

Теперь самое время приступить к сканированию сети. Создадим функцию get_ip_mac_nework(ip), которая на входе будет получать начальный адрес для сканирования с маской подсети, а возвращать список из словарей с полученными значениями ip и mac адресов. Сделаем с помощью функции scapy.srp широковещательный ARP запрос, чтобы получить ответы от всех компьютеров, которые находятся с нами в одной сети. Для отправки запроса нужно сформировать пакет, который будет состоять из широковещательного адреса и ARP-пакета, в котором указывается адрес и маска подсети. Параметр timeout указывает на то, что между запросами нужно делать паузу в одну секунду, а параметр verbose позволяет не выводить результаты выполнения запроса в терминал.

answered_list = sc.srp(sc.Ether(dst='ff:ff:ff:ff:ff:ff') / sc.ARP(pdst=ip), timeout=1, verbose=False)[0]

С помощью данного запроса мы можем получить два списка. С запросами, на которые был получен ответ и с запросами, на которые ответ не был получен. Так как нам не отвеченные запросы вроде бы без надобности, забираем только те, ответ на которые был получен. Потому, указываем явно, что забираем 0 элемент.

Далее в цикле пробежимся по содержимому списка с запросами и получим из него нужные значения, которые добавим в список, возвращаемый из функции в виде словаря.

Python:
def get_ip_mac_nework(ip):
    answered_list = sc.srp(sc.Ether(dst='ff:ff:ff:ff:ff:ff') / sc.ARP(pdst=ip), timeout=1, verbose=False)[0]
    clients_list = []
    for element in answered_list:
        clients_list.append({'ip': element[1].psrc, 'mac': element[1].hwsrc})
    return clients_list


Печать результатов сканирования

Ну и осталось вывести полученные результаты на экран. В данном случае за это будет отвечать функция print_ip_mac(mac_ip_list), которая на входе получает сформированный ранее список со словарями. Тут все просто. Пробегаемся в цикле по списку и получаем определенные значений из словарей под нужным индексом. После чего выводим на экран.

Код функции печати:

Python:
def print_ip_mac(mac_ip_list):
    print(f"\nMachine in Network:\n\nIP\t\t\t\t\tMAC-address\n{'-' * 41}")
    for client in mac_ip_list:
        print(f'{client["ip"]}\t\t{client["mac"]}')


Запуск сканирования сети

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

Код функции main():

Python:
def main():
    local_ip = local_ipv4()
    if system() == "Windows":
        gateway = get_gateway_win()
    elif system() == 'Linux':
        if not os.getuid() == 0:
            print('\n[+] Запустите скрипт с правами суперпользователя!\n')
            return
        gateway = get_gateway_linx()
    ip_mac_network = get_ip_mac_nework(f'{local_ip.split(".")[0]}.{local_ip.split(".")[1]}.{local_ip.split(".")[2]}.1/24')
    print(f'\n[+] Local IP: {local_ip}\n[+] Local Gateway: {gateway}')
    print_ip_mac(ip_mac_network)

А на этом, в данном случае все. Сканер сети готов, осталось только посмотреть на результаты его работы. Запускаем и видим, что в нашей локальной сети есть межсетевой экран. А те, кто понимает, даже узнали, что это pfSense, а также три машины ip и mac адреса которых были благополучно получены.

Результат работы сканера:

screenshot2.png


Python:
from platform import system
from socket import socket, AF_INET, SOCK_DGRAM, gethostbyname
from subprocess import check_output
import os

import scapy.all as sc


# получаем локальный IP-адрес
def local_ipv4():
    st = socket(AF_INET, SOCK_DGRAM)
    try:
        st.connect(('10.255.255.255', 1))
        ip_l = st.getsockname()[0]
    except Exception:
        ip_l = '127.0.0.1'
    finally:
        st.close()
    return ip_l


def get_gateway_win():
    # получаем адрес шлюза по умолчанию для текущего сетевого интерфейса в Windows
    com = f'route PRINT 0* | findstr {local_ipv4()}'.split()
    return check_output(com, shell=True).decode('cp866').split()[2]


# получение ip адреса шлюза используемого по умолчанию в Linux
def get_gateway_linx():
    ip_route = str(check_output('route -n | grep UG', shell=True).decode()).split()[1].strip()
    return ip_route


# сканируем сеть, получаем ip и mac сетевых машин
def get_ip_mac_nework(ip):
    answered_list = sc.srp(sc.Ether(dst='ff:ff:ff:ff:ff:ff') / sc.ARP(pdst=ip), timeout=1, verbose=False)[0]
    clients_list = []
    for element in answered_list:
        clients_list.append({'ip': element[1].psrc, 'mac': element[1].hwsrc})
    return clients_list


# функция печати сканированных ip и mac
def print_ip_mac(mac_ip_list):
    print(f"\nMachine in Network:\n\nIP\t\t\t\t\tMAC-address\n{'-' * 41}")
    for client in mac_ip_list:
        print(f'{client["ip"]}\t\t{client["mac"]}')


def main():
    local_ip = local_ipv4()
    if system() == "Windows":
        gateway = get_gateway_win()
    elif system() == 'Linux':
        if not os.getuid() == 0:
            print('\n[+] Запустите скрипт с правами суперпользователя!\n')
            return
        gateway = get_gateway_linx()
    ip_mac_network = get_ip_mac_nework(f'{local_ip.split(".")[0]}.{local_ip.split(".")[1]}.{local_ip.split(".")[2]}.1/24')
    print(f'\n[+] Local IP: {local_ip}\n[+] Local Gateway: {gateway}')
    print_ip_mac(ip_mac_network)


if __name__ == "__main__":
    main()

Ну и да, не забывайте о том, что именно таким образом вам удастся просканировать только локальную сеть.

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

Вложения

  • network_scanner_lin_win.zip
    1,1 КБ · Просмотры: 67
Последнее редактирование:

qwe123xcv

Green Team
16.11.2019
44
16
Я лишь бегло просмотрел код скрипта и возникает вопрос:
Что это за адрес, к которому ты выполняешь подключение ?
Код:
# получаем локальный IP-адрес
def local_ipv4():
    st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        st.connect(('10.255.255.255', 1))
        ip_l = st.getsockname()[0]
    except Exception:
        ip_l = '127.0.0.1'
    finally:
        st.close()
    return ip_l

Ты решил, что все со всём мире частные сети имеют адреса 10.***.***.*** ?

Кроме того, импорт библиотек в самом начале скрипта вызывает вопросы.
Зачем импортировать всю библиотеку socket, если пользуешься не всеми её компонентами ? Импортируй только те элементы , которые задействуешь в скрипте.
И так с каждой импортируемой библиотекой.
Разве так не правильнее сделать ?


Что с правами суперпользователя в линукс ? Твой скрипт запустится от имени обычного юзера ? Если ответ отрицательный, то почему в скрипте нет проверки прав пользователей ?
 

Johan Van

Green Team
13.06.2020
153
280
Я лишь бегло просмотрел код скрипта и возникает вопрос:
Что это за адрес, к которому ты выполняешь подключение ?
Код:
# получаем локальный IP-адрес
def local_ipv4():
    st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        st.connect(('10.255.255.255', 1))
        ip_l = st.getsockname()[0]
    except Exception:
        ip_l = '127.0.0.1'
    finally:
        st.close()
    return ip_l

Ты решил, что все со всём мире частные сети имеют адреса 10.***.***.*** ?

Кроме того, импорт библиотек в самом начале скрипта вызывает вопросы.
Зачем импортировать всю библиотеку socket, если пользуешься не всеми её компонентами ? Импортируй только те элементы , которые задействуешь в скрипте.
И так с каждой импортируемой библиотекой.
Разве так не правильнее сделать ?


Что с правами суперпользователя в линукс ? Твой скрипт запустится от имени обычного юзера ? Если ответ отрицательный, то почему в скрипте нет проверки прав пользователей ?

Адрес, к которому выполняется подключение, это, как раз-таки, внутренняя частная подсеть провайдера. В большинстве случаев данный частный адрес будет работать. Но по сути, UDP сокет можно запустить на любой внешний адрес, к примеру, на: 8.8.8.8

А можно сделать так, дополнительно установив пару библиотек:

Python:
from getmac.getmac import _get_default_iface_linux
from netifaces import ifaddresses, AF_INET

print(ifaddresses(_get_default_iface_linux()).setdefault(AF_INET)[0]['addr'])

Добавить сюда еще try-except, на случай, если вдруг нет сети. Тогда будет выглядеть так:

Python:
def local_ipv4():
    try:
        return ifaddresses(_get_default_iface_linux()).setdefault(AF_INET)[0]['addr']
    except TypeError:
        return '127.0.0.1'

Но, это только для Linux. Для Windows такой способ не подойдет. Там все чуточку замудренее. Код попросту усложниться. Для Windows получить имя сетевого интерфейса по умолчанию можно вот так:

Python:
def get_interface_win():
    com = 'getmac /FO csv /NH /V'.split()
    address = getmac.getmac.get_mac_address().replace(":", "-").upper()
    interface_temp = subprocess.check_output(com, shell=False).decode('cp866')
    with open('temp.txt', 'w') as file:
        file.write(interface_temp)
    with open('temp.txt') as file:
        src = file.readlines()
    os.remove('temp.txt')
    for line in src:
        if address in line:
            return line.split(",")[0].replace('"', ""), line.split(",")[-1].replace('"', "").split("_")[-1].strip()

Но, может быть можно как-то по другому. Не знаю. И тогда в ifaddresses уже будет получаться для return функции get_interface_win(). Если я не ошибаюсь, конечно.

По поводу правильности импортирования. Вполне возможно, так будет экономичнее в плане ресурсов. Не сказал бы, что это критично. И не всегда нужно. Потому, что если в двух модулях будут одинаковые функции, придется задавать им псевдонимы.

Да, скрипт не запуститься из под простого пользователя в Linux. Но обязательно ли делать проверку? Впрочем, это не сложно. Добавил проверку в код.
 
Последнее редактирование:
  • Нравится
Реакции: And4R и Dart Moore

Архонт

Green Team
03.12.2019
19
7
Ты решил, что все со всём мире частные сети имеют адреса 10.***.***.*** ?
This method returns the "primary" IP on the local box (the one with a default route).


  • Does NOT need routable net access or any connection at all.
  • Works even if all interfaces are unplugged from the network.
  • Does NOT need or even try to get anywhere else.
  • Works with NAT, public, private, external, and internal IP's
  • Pure Python 2 (or 3) with no external dependencies.
  • Works on Linux, Windows, and OSX.
отсюда Finding local IP addresses using Python's stdlib
 
  • Нравится
Реакции: Johan Van

Polyglot

Green Team
25.02.2020
27
40
Как @Архонт отметил, код для определения адреса взят с stackoverflow. И проблема с ним что он НЕ пытается НИКУДА соединиться,
сделай снифер и не увидишь ни одного пакета посланного на 10.255.255.255 (broadcast), даже если есть карта в этой сети. В посте на SO
на вопрос "почему" и "как это работает" сам автор не смог ответить, то есть это уродливый хак непонятно как работающий.
А любой кодер с опытом знает аксиому " Не копи-пасти код который не понимаешь". Понятно что ты сам учишься и все
прошли через это, но стоит постараться для себя же не лепить Франкенштейн-код с SO, а писать/использовать только после того как сам
разобрался как это работает.
 
  • Нравится
Реакции: zvepb

Johan Van

Green Team
13.06.2020
153
280
Как @Архонт отметил, код для определения адреса взят с stackoverflow. И проблема с ним что он НЕ пытается НИКУДА соединиться,
сделай снифер и не увидишь ни одного пакета посланного на 10.255.255.255 (broadcast), даже если есть карта в этой сети. В посте на SO
на вопрос "почему" и "как это работает" сам автор не смог ответить, то есть это уродливый хак непонятно как работающий.
А любой кодер с опытом знает аксиому " Не копи-пасти код который не понимаешь". Понятно что ты сам учишься и все
прошли через это, но стоит постараться для себя же не лепить Франкенштейн-код с SO, а писать/использовать только после того как сам
разобрался как это работает.

Спасибо, я постараюсь (y)
 
  • Нравится
Реакции: Polyglot

Архонт

Green Team
03.12.2019
19
7
но мне все таки интересно, как узнать IP с помощью питона и стандартных модулей?
ничего адекватного я не нашел. нет ни в модулях os, ни в platform
 

Johan Van

Green Team
13.06.2020
153
280
но мне все таки интересно, как узнать IP с помощью питона и стандартных модулей?
ничего адекватного я не нашел. нет ни в модулях os, ни в platform

Здравствуйте. Я не задавался этим вопросом, если честно. Но, может быть попробовать в Windows вот так:

Python:
import subprocess

com = 'route PRINT 0* -4 | findstr 0.0.0.0'.split()
interface_temp = subprocess.check_output(com, shell=True).decode('cp866')
locail_ip = interface_temp.split()[-2]
default_gateway = interface_temp.split()[-3]
print(f'Local IP: {locail_ip}\nDefault Gateway: {default_gateway}')

Потестировать пока не могу, с компьютером были небольшие проблемы. Потому, все виртуалки удалил. По той же причине нет пока Linux. Но, на моей машине код который выше сработал. По Linux посмотрю немного позже. Уже когда все установлю.
 
Последнее редактирование:

zvepb

Green Team
04.04.2020
146
125
но мне все таки интересно, как узнать IP с помощью питона и стандартных модулей?
ничего адекватного я не нашел. нет ни в модулях os, ни в platform
Можно выполнить команду ifconfig/ipconfig через стандартный subprocess и достать там.
 

Архонт

Green Team
03.12.2019
19
7
ну... винда не интересует особо. нет ее у меня. хотелось бы кроссплатформенное что-то
 

Johan Van

Green Team
13.06.2020
153
280
ну... винда не интересует особо. нет ее у меня. хотелось бы кроссплатформенное что-то

Ну тогда не знаю. Только с использование socket получается. Если бы вы подсказали, было бы здорово. Ведь учиться у тех, кто знает больше это хорошо.
 
Последнее редактирование:

zvepb

Green Team
04.04.2020
146
125
ну... винда не интересует особо. нет ее у меня. хотелось бы кроссплатформенное что-то
Выше есть решение, если вам необходимо узнать свой ай пи адрес с помощью питона и стандартных либов, можете придумать что-то более изощренное или воспользоваться socket, и не делать мозги)

Спасибо за подсказку. Но это в Linux. Для Windows мне показалось проще через "route". Я сейчас поставлю виртуалки и все потестирую.
Точно также сработает и на винде, только не думаю что это хорошее решение)
 

Johan Van

Green Team
13.06.2020
153
280
Точно также сработает и на винде, только не думаю что это хорошее решение)

Ну в общем-то да. Потому как я читал, что вывод команды ipconfig/ifconfig может различаться у операторов в разных странах. Не знаю, как это выглядит на самом деле. Но, тем не менее... Если локальное решение здесь и сейчас, чтобы работало, то думаю подойдет. Если универсальное - то скорее всего нет.
 
  • Нравится
Реакции: zvepb

zvepb

Green Team
04.04.2020
146
125
Так твой скрипт в итоге работает или нет, как на скриншотах?)
 

Johan Van

Green Team
13.06.2020
153
280
Так твой скрипт в итоге работает или нет, как на скриншотах?)

Да, все работает. Тут суть в том, что человеку не понравилось, что я скопипастил код со стековерфлоу. Но на самом деле я не знаю, где его взял. Не помню просто. Тем не менее, я не стал досконально разбираться в том, как он работает. Это конечно плохо. Но, на тот момент мне было важно, что он работает. Потому не смог толком ответить на вопрос пользователя.
 

Архонт

Green Team
03.12.2019
19
7
Выше есть решение, если вам необходимо узнать свой ай пи адрес с помощью питона и стандартных либов, можете придумать что-то более изощренное или воспользоваться socket, и не делать мозги)
есть вопросы к решению, которое описано выше.
Как @Архонт отметил, код для определения адреса взят с stackoverflow. И проблема с ним что он НЕ пытается НИКУДА соединиться,
сделай снифер и не увидишь ни одного пакета посланного на 10.255.255.255 (broadcast), даже если есть карта в этой сети. В посте на SO
на вопрос "почему" и "как это работает" сам автор не смог ответить, то есть это уродливый хак непонятно как работающий.
А любой кодер с опытом знает аксиому " Не копи-пасти код который не понимаешь". Понятно что ты сам учишься и все
прошли через это, но стоит постараться для себя же не лепить Франкенштейн-код с SO, а писать/использовать только после того как сам
разобрался как это работает.
поэтому и возник вопрос: разве нет такого в стандартных модулях языка? зачем городить забор и изобретать велосипед?
 

Johan Van

Green Team
13.06.2020
153
280
есть вопросы к решению, которое описано выше.

поэтому и возник вопрос: разве нет такого в стандартных модулях языка? зачем городить забор и изобретать велосипед?

Ну так я же не отрицаю того, что не смог ответить на вопрос, потому, что не разобрался. Но, тем не менее, спасибо за науку. Для себя наковырял довольно удобных команд в обеих системах :geek:
 
Мы в соцсетях: