Очень часто, в начале изучения Python, многие сталкиваются с необходимостью поиска способа получения тех или иных параметров сетевого интерфейса, используемого операционной системой по-умолчанию. Конечно же, поиск - это дело хорошее, но учитывая свой небольшой опыт, я попытался собрать код, с помощью которого можно это сделать, в этой статье.
Начнем с самого простого, но, в случае с питоном, как оказалось, такого неоднозначного способа получения локального IP-адреса используемого сетевым интерфейсом по-умолчанию. В стандартной документации сказано, что можно использовать следующий код:
Однако, в большинстве случаев, вывод данного кода в терминале будет выглядеть так:
то есть, мы увидим адрес локальной петли (localhost). А это не является настоящим адресом для сетевого адаптера по-умолчанию. Значит, необходимо искать другие, более рабочие способы.
Код со Stack Overflow для получения локального IP-адреса
Данный код я нашел когда озадачился получением локального IP-адреса, на просторах Stack Overflow. Уже не помню точно, что это была за статья, но, думаю, что вам не составит труда найти данный код на сайте самостоятельно. Я же приведу вам его здесь, дабы избавить от утомительных поисков. Как было сказано в его описании, он работает в любой операционной системе, будь то Linux, Windows или MacOS. Для его работы не требуется установки сторонних библиотек. И на данный момент код все еще работает.
Но, использовать его нужно с некоторой осторожностью, так как, изменения, происходящие как в ОС, так и в самом питоне, могут привести к неоднозначности его вывода. Вот сам код:
Здесь мы выполняем UDP соединение по адресу 10.255.255.255, после чего получаем имя сокета, которое в данном случае и является локальным ip-адресом. Однако, на всякий случай необходимо предусмотреть возникновение исключений и их обработку с помощью блока try — except, дабы вернуть тот же самый адрес локальной петли.
Получение локального IP-адреса с помощью парсинга команд Linux и Windows
Данный код я написал самостоятельно. В нем нет ничего особо оригинального, просто используется банальный парсинг вывода команд одной из операционных систем. Данный код в настоящее время тестировался на Linux (Ubuntu, Kali и Mint), а также на Windows 10. Требуется тестирование на других дистрибутивах Linux, чтобы сказать однозначно, будет ли он работать. А также тестирование на версиях Windows младше или старше десятки.
В данном коде определяется, какая ОС работает в текущий момент. В зависимости от этого выполняются разные команды и парситься их вывод.
Для Linux это команда: ip -h -br a | grep UP, в которой фильтруется вывод по наличию слова UP, что означает, что данный интерфейс активен. На самом деле, хорошо было бы предусмотреть получение адреса из конфигурационных файлов. Но, здесь тоже все не так однозначно, так как на разных системах наличие файлов в директориях может различаться.
Для Windows парситься вывод команды: wmic nicconfig get IPAddress, IPEnabled /value, В данном случае получаем два параметра, один из которых является IP-адресом, а второй признаком работы сетевого интерфейса. Если интерфейс включен, значение параметра IPEnabled будет TRUE. Так как данная команда возвращает данные по всем интерфейсам в системе, важно отфильтровать не нужные значения и получить данные только о работающем интерфейсе, который и будет искомым по-умолчанию.
Получение локального IP-адреса с помощью сторонних библиотек getmac и netifaces
Для использования данного кода уже требуется установка сторонних библиотек, таких как getmac и netifaces. Устанавливаются они командой:
Однако, данная команда справедлива только для Linux. В ОС Windows установку библиотеки netifaces лучше производить вручную, так как она не хочет устанавливаться и компилироваться с помощью стандартной установки. Я решил эту проблему с помощью сайта:
Но и здесь не все так просто. Мною было обнаружено, что последняя библиотека доступная на данном сайте была скомпилирована для python версии 3.9, а значит, на версии 3.10 и 3.11 она уже не установиться.
Здесь можно попробовать скачать архив с PyPi и самостоятельно создать установочный файл. Либо дождаться версии для последнего питона, ну, или использовать питон версии 3.9, в случае, если это не особо принципиально.
Работа данной функции основана на работе двух библиотек. Одна позволяет получить в случае с Linux имя сетевого интерфейса по-умолчанию, то есть, библиотека getmac и передать полученное имя в функцию ifaddresses библиотеки netifaces. Она то и возвращает ip-адрес для сетевого интерфейса.
В случае с Windows, с помощью функции getmac получаем mac-адрес, который используется сетевым интерфейсом по-умолчанию. Приводим его в соответствии с тем, в каком виде он отображается в ОС Windows, то есть, меняем разделители.
Затем выполняем команду: getmac /FO csv /NH /V, выполняем ее парсинг, откуда, на основании сравнения полученного mac-адреса, забираем имя сетевого интерфейса. Если в случае с Linux имя может быть, для примера, eth0, то, в случае с Windows это имя будет заключено в фигурные скобки и выглядеть примерно так: {4D36E972-E325-11CE-BFC1-08002BE10318}. Именно это полученное имя сетевого интерфейса мы передаем в функцию ifaddresses библиотеки netifaces, откуда уже получаем ip-адрес сетевого интерфейса по-умолчанию.
На этом описание способов получения локального IP-адреса сетевого интерфейса по-умолчанию можно завершить. В принципе, описанных способов, должно хватить. Это не означает, что других способов не существует. Просто, если вам будет недостаточно описанного, можно сделать функцию самостоятельно, предварительно поискав в сети варианты получения адреса.
Получение IP-адреса шлюза по-умолчанию используемого в ОС
Иногда для использования в проектах нужно понимать, какой ip-адрес используется для шлюза в операционной системе. Для примера, при работе с библиотекой scapy. Для себя, данную задачу я решил в коде, который представлен ниже. Не скажу, что это универсальный способ. Он так же требует тестирования на различных дистрибутивах Linux и версиях ОС Windows. Также, в некоторых дистрибутивах нет модуля route, поэтому его нужно будет установить дополнительно. Для примера в Ubuntu его не было и пришлось устанавливать с помощью команды:
Больше, кроме указанного модуля, установки сторонних библиотек не требуется. Скрипт получает данные выполняя команды ОС с помощью subprocess.check_output и забирает из них вывод, который впоследствии просто парситься.
Также в данный код были добавлены дополнительные функции для проверки на соответствие полученного результата ip-адресу. Так как бывает, что возвращается не адрес, а доменное имя. У меня так было когда я использовал pfsence в качестве шлюза. Если проверка на ip-адрес не проходит, пробуем получить адрес по доменному имени. В случае же неудачи возвращаем то, что и получили изначально.
Получение сводной информации по сетевому интерфейсу используемому по-умолчанию
На этом получение разного рода адресов локальных сетевых интерфейсов можно завершить. И подытожить небольшой функцией, которая возвращает сводную информацию по сетевому интерфейсу, а именно: его имя, ip-адреса v4 и v6, адрес шлюза, а также mac-адрес.
Для этой цели я создал небольшую функцию, которая для работы не требует установки сторонних библиотек, а использует парсинг команд операционной системы. В связи с этим, эта функция также требует тестирования.
Думаю, что описывать полный процесс парсинга здесь не имеет особого смысла. Вам достаточно получить вывод используемой команды и попытаться распарсить его самостоятельно. Вполне возможно, что это у вас получиться более изящным способом. У меня это на данный момент выглядит так, как представлено в коде, но, тем не менее — работает.
Получение внешнего(публичного) IP-адреса вашей сети
Также как и локальный IP-адрес, внешний IP довольно часто используется в самых разнообразных проектах. И потому, его получение это довольно важная задача. Впрочем, решается она довольно просто. Выполняется запрос к api сервиса и забирается ответ в виде внешнего ip-адреса. Я использовал здесь
Теперь же, давайте перейдем непосредственно к коду:
Выполняем запрос к api сервиса, получаем ответ в виде текста и возвращаем его из функции. Здесь не требуется парсить возвращаемый код, так как данные прилетают уже в текстовом виде. Ну и обернем все это в блок try - except, который будет отрабатывать в случае возникновения исключений. Как вариант, если у вас отсутствует интернет, будет вызвано исключение и вам просто вернется адрес локальной петли: 127.0.0.1.
Получение ip-адреса домена по его имени, имени домена по ip-адресу, а также названий сервисов работающих на определенном порту
Код, который выполняет получение всех данных, которые описаны в заголовке я решил объединить в один блок, так все функции в нем используют те или иные методы библиотеки socket. На самом деле, все это достаточно просто найти в интернете, но, когда это под рукой, да еще и с реализованной проверкой, становиться гораздо проще использовать уже готовый код, а не писать каждый раз все с нуля.
Давайте начнем с функции, которая получает ip-адрес домена.
Как видите, используется метод библиотеки socket — gethostbyname. Но, перед тем как получить ip-адрес, необходимо выполнить проверку, не является ли переданное имя домена, собственно ip-адресом. Такое иногда случается. Выполняем данную проверку с помощью функции IPv4Address библиотеки ipaddress. Если переданное в функцию значение является ip-адресом, мы просто возвращаем его из функции, так как выполнять получение того, что уже получено не имеет смысла.
Если же переданное значение ip-адресом не является, возникает исключение и мы проверяем домен на наличие в нем протоколов и прочих путей, которые в данном контексте излишни. Производим очистку и на всякий случай проверяем, не является ли домен, полученный после очистки субдоменом. Если да, в этом случае забираем из него домен и уже полученное после очистки имя домена передаем дальше. Ну и в случае успешного получения ip-адреса возвращаем его из функции, если же возникло исключение, возвращаем None.
Следующая функция — получение домена по ip-адресу. Работает корректно не всегда, зачастую возвращаются PTR записи в формате arpa.
В данном коде используется метод gethostbyaddr библиотеки socket. Здесь также выполняем проверку на принадлежность полученных данных к ip-адресу, если это не ip-адрес, просто выходим из функции. Если же адрес корректен, выполняем попытку получения имени домена. Ну и также обрабатываем исключение, которое может возникнуть при получении имени домена.
Ну и последняя функция в данном блоке, это функция получения имени сервиса, который работает на определенном порту. Может быть полезна, когда вы выполняете сканирование портов, для получения имени сервиса работающего на открытом потру.
В данном коде выполняется попытка получения имени сервиса с помощью метода getservbyport библиотеки socket. В случае неудачи возвращаем unknown, так как возвращаются далеко не все имена портов. Ну и здесь добавлена функция проверки типа полученных данных. То есть, кто-то может передать порт как целое число, а кто-то как простую строку. Ну мало ли, всякое бывает. Вот тут и проверяется тип данных. И выполняется попытка привести данные к нужному виду для передачу в функцию.
То есть, в данном случае использовать данный код нужно как-то так:
Ну или можно добавить предварительный вызов функции непосредственно в функции serv_on_port. Тут уже по вашему желанию.
А на этом с портами, доменами и их ip-адресами можно закончить.
Пинг домена или IP-адреса
Да, здесь пойдет речь о простом пинге доменов и адресов. Можно конечно использовать subprocess и распарсить его вывод, но зачем изобретать велосипед, если для этого есть удобная библиотека. Для работы данного кода требуется установка библиотеки ping3:
Нужно иметь в виду, что это простой пинг и о высокой точности обнаружения работоспособного домена ожидать здесь не стоит, так как некоторые домены, особенно те, которые находятся за CDN, просто ничего не возвращают, так как у них стоит запрет пинга. Для примера можете выполнить пинг на адрес codeby.net и вы увидите, что все отправленные пакеты были благополучно потеряны. А все потому, что данный домен находиться за CDN ddos-guard.net. Хотя, на самом деле странно. В информации whois все еще значиться cloudflare. Но, думаю, что это просто хитрый ход админов )). Вот, сам код:
Как видите, здесь также не происходит ничего особого. Для начала выполняется проверка, что же мы получили на вход функции. Если ip — пропускаем, если домен, пытаемся обработать, прежде чем отправить в функцию пинга. Ну и, собственно, сам пинг, с помощью модуля ping. Если пинг адреса прошел удачно, возвращается время, за которое был получен ответ. Если же нет, возвращается None. В зависимости от этого возвращаем True или False из функции.
Получение координат ip-адреса
На самом деле не особо полезная функция, так как выдает достаточно приблизительный результат. Но, тем не менее использовать ее в работе можно, хоть и с очень большими погрешностями. Для примера, если я попытаюсь определить координаты моего ip, я получу в ответ широту и долготу, которая удалена от меня, примерно на полторы тысячи километров. Тем не менее, поехали. Для работы данного скрипта потребуется установка библиотеки requests:
С ее помощью мы будем выполнять запросы к api сервиса
Вы можете возвращать это как-то по-другому. Мне нужно было возвращать координаты в таком виде, когда я использовал их в другой функции, которая будет представлена ниже. А в нее передавать координаты необходимо именно списком. Потому, так получилось. Ну я так и оставил.
Получение физического адреса по координатам
Для работы данного скрипта потребуется установка библиотеки geopy:
В функцию представленную ниже передаются координаты объекта, адрес которых требуется получить. Данные передаются в виде списка, который представляет из себя широту и долготу. Также для переменной location предусмотрено значение по-умолчанию, для того, чтобы исключить ошибки в работе. Поэтому, проверяем, что содержится в данной переменной и уж только потом передаем данные в модуль Nominatim. Указываем user_agent. Здесь он на самом деле может быть любой. Затем указываем, что хотим получить обратную геолокацию с помощью функции reverse, и с забираем из полученных данных адрес. Если же возникает исключение, возвращаем None. В работе данной библиотеки используются данные OpenStreetMap, а потому, некоторые адреса получить не представляется возможным. Но, это скорее исключение, чем правило. В основном библиотека отрабатывает на пять баллов.
Вот, собственно, те функции, которыми я хотел с вами поделиться сегодня. Наверняка их список далеко не полный и его можно расширять. Но, о других данных попробуем поговорить уже в другой статье. Ну и да, я для теста все файлы с функциями скидал в отдельную папочку, которую обозначил как модуль. Таким образом, для использования в своем коде я могу либо вызвать функцию из модуля, либо просто скопировать файл с функцией к себе в папку с кодом. Либо скопировать сам код и вставить в нужный скрипт.
Сделал небольшой скрипт для теста, который вызывает по очереди все выше представленные функции.
Особо с кодом не заморачивался, так как он служит только лишь для демонстрации работы. Ну и вывод функции на виртуалке с Windows:
Ну и на реальной машине с Linux Mint. Некоторые данные пришлось заблюрить:
А на этом все.
Код описанный в статье доступен во вложении.
Спасибо за внимание. Надеюсь информация будет вам полезна
Начнем с самого простого, но, в случае с питоном, как оказалось, такого неоднозначного способа получения локального IP-адреса используемого сетевым интерфейсом по-умолчанию. В стандартной документации сказано, что можно использовать следующий код:
Python:
from socket import gethostbyname, gethostname
print(gethostbyname(gethostname()))
Однако, в большинстве случаев, вывод данного кода в терминале будет выглядеть так:
127.0.0.1
то есть, мы увидим адрес локальной петли (localhost). А это не является настоящим адресом для сетевого адаптера по-умолчанию. Значит, необходимо искать другие, более рабочие способы.
Код со Stack Overflow для получения локального IP-адреса
Данный код я нашел когда озадачился получением локального IP-адреса, на просторах Stack Overflow. Уже не помню точно, что это была за статья, но, думаю, что вам не составит труда найти данный код на сайте самостоятельно. Я же приведу вам его здесь, дабы избавить от утомительных поисков. Как было сказано в его описании, он работает в любой операционной системе, будь то Linux, Windows или MacOS. Для его работы не требуется установки сторонних библиотек. И на данный момент код все еще работает.
Но, использовать его нужно с некоторой осторожностью, так как, изменения, происходящие как в ОС, так и в самом питоне, могут привести к неоднозначности его вывода. Вот сам код:
Python:
"""
Возвращает локальный ip-адрес на всех ОС.
Не требует установки сторонних библиотек.
Код взят со SO.
"""
from socket import socket, AF_INET, SOCK_DGRAM
def local_ipv4() -> str:
"""
Получаем локальный IP-адрес с помощью установки
соединения на адрес 10.255.255.255. В ответ получаем
имя сокета, которое и является адресом.
:return: возвращает локальный IP-адрес или адрес локальной петли
в случае возникновения исключения.
"""
ip = None
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
Здесь мы выполняем UDP соединение по адресу 10.255.255.255, после чего получаем имя сокета, которое в данном случае и является локальным ip-адресом. Однако, на всякий случай необходимо предусмотреть возникновение исключений и их обработку с помощью блока try — except, дабы вернуть тот же самый адрес локальной петли.
Получение локального IP-адреса с помощью парсинга команд Linux и Windows
Данный код я написал самостоятельно. В нем нет ничего особо оригинального, просто используется банальный парсинг вывода команд одной из операционных систем. Данный код в настоящее время тестировался на Linux (Ubuntu, Kali и Mint), а также на Windows 10. Требуется тестирование на других дистрибутивах Linux, чтобы сказать однозначно, будет ли он работать. А также тестирование на версиях Windows младше или старше десятки.
Python:
"""
Скрипт предназначен для получения локального ip-адреса с помощью стандартных средств
операционной системы, в зависимости от того, какая операционная система запущена
в данный момент. Работает на Linux и Windows. Выполняется парсинг вывода стандартных команд.
Данный код требует более детальной проверки на различных дистрибутивах ОС Linux, так как
вывод команды может немного отличатся. Проверен на: Kali, Ubuntu, Mint.
Для Windows, также требуется проверка на версиях ОС младше и старше 10 версии. В данный
момент протестирован на Windows 10 Pro.
Установки дополнительных библиотек для работы не требует.
"""
from platform import system
from subprocess import check_output
def local_ipv4() -> (str, None):
"""
Выполняется определение ОС. В зависимости от этого выполняется
команда с помощью subprocess.check_output для получения локального
ip-адреса. Парситься и возвращается вывод команд. Если в процессе
выполнения команды поднимается исключение, возвращается None.
:return: строковый параметр - локальный ip-адрес или None.
"""
if system() == "Linux":
try:
return check_output('ip -h -br a | grep UP', shell=True).decode().split()[2].split("/")[0]
except Exception:
return
elif system() == "Windows":
try:
net_set = check_output("wmic nicconfig get IPAddress, IPEnabled /value"). decode("cp866").strip().\
split("\r\r\n")
text = ""
for net in net_set:
if net.strip() == "":
text = f"{text}|"
else:
text = f"{text}~{net.strip()}"
text = text.strip().split("||")
for tx in text:
if tx.split("~")[-1].split("=")[1] != "TRUE":
continue
for item in tx.split("~"):
if item.strip() == "":
continue
if item.strip().split("=")[0] == "IPEnabled":
continue
if item.strip().split("=")[1] != "":
if item.strip().split("=")[0] == "IPAddress":
return item.strip().split("=")[1].split(",")[0].replace('"', '').replace("{", "")
except Exception:
return
В данном коде определяется, какая ОС работает в текущий момент. В зависимости от этого выполняются разные команды и парситься их вывод.
Для Linux это команда: ip -h -br a | grep UP, в которой фильтруется вывод по наличию слова UP, что означает, что данный интерфейс активен. На самом деле, хорошо было бы предусмотреть получение адреса из конфигурационных файлов. Но, здесь тоже все не так однозначно, так как на разных системах наличие файлов в директориях может различаться.
Для Windows парситься вывод команды: wmic nicconfig get IPAddress, IPEnabled /value, В данном случае получаем два параметра, один из которых является IP-адресом, а второй признаком работы сетевого интерфейса. Если интерфейс включен, значение параметра IPEnabled будет TRUE. Так как данная команда возвращает данные по всем интерфейсам в системе, важно отфильтровать не нужные значения и получить данные только о работающем интерфейсе, который и будет искомым по-умолчанию.
Получение локального IP-адреса с помощью сторонних библиотек getmac и netifaces
Для использования данного кода уже требуется установка сторонних библиотек, таких как getmac и netifaces. Устанавливаются они командой:
pip install getmac netifaces
Однако, данная команда справедлива только для Linux. В ОС Windows установку библиотеки netifaces лучше производить вручную, так как она не хочет устанавливаться и компилироваться с помощью стандартной установки. Я решил эту проблему с помощью сайта:
Ссылка скрыта от гостей
, где помимо данной библиотеки доступны для скачивания и установки еще множество других библиотек.Но и здесь не все так просто. Мною было обнаружено, что последняя библиотека доступная на данном сайте была скомпилирована для python версии 3.9, а значит, на версии 3.10 и 3.11 она уже не установиться.
Здесь можно попробовать скачать архив с PyPi и самостоятельно создать установочный файл. Либо дождаться версии для последнего питона, ну, или использовать питон версии 3.9, в случае, если это не особо принципиально.
Python:
"""
Возвращает локальный ip-адрес на Linux и Windows машинах.
К сожалению, обойтись стандартными библиотеками в данном
случае не удалось.
Для работы скрипта необходима установка библиотек:
pip install getmac netifaces
Из-за сложностей компилирования библиотеки netifaces
в ОС Windows установка стандартными способами оканчивается
ошибкой. Для установки данной библиотеки необходимо скачать
ее скомпилированную версию с сайта: https://www.lfd.uci.edu/~gohlke/pythonlibs/#netifaces,
после чего установить командой:
pip install modul_name.whl
Для примера: pip install netifaces-0.11.0-cp39-cp39-win_amd64.whl
Однако, на данный момент на сайте присутствуют библиотеки только для python 3.9.
Установка на python более поздних версий окончиться неудачей.
В Linux подобной проблемы не наблюдается. Все библиотеки устанавливаются стандартно.
Есть возможность скачать из репозитория PyPi архив и создать пакет самостоятельно.
"""
from platform import system
from subprocess import check_output
from getmac.getmac import _get_default_iface_linux, get_mac_address
from netifaces import ifaddresses, AF_INET
def local_ipv4() -> str:
"""
Выполняется определение версии ОС. В зависимости от этого
запускается код по получению локального ip-адреса.
Для ОС Linux название сетевого интерфейса получаем с помощью
модуля _get_default_iface_linux библиотеки getmac.
Затем, полученное имя передается в функцию ifaddresses библиотеки
netifaces, откуда и забирается вывод.
Для ОС Windows имя сетевого интерфейса получаем не совсем стандартным
способом. Для начала, с помощью функции get_mac_address библиотеки getmac
получаем mac-адрес интерфейса по-умолчанию. Затем, чтобы получить имя сетевого
интерфейса выполняем команду getmac и парсим вывод, забирая только те данные
в которых есть mac-адрес полученный ранее. Далее, полученное имя интерфейса
передаем в функцию ifaddresses и забираем вывод.
:return: возвращает локальный ip-адрес или адрес локальной петли в случае ошибки
получения данных.
"""
if system() == "Linux":
try:
return ifaddresses(_get_default_iface_linux()).setdefault(AF_INET)[0]['addr']
except TypeError:
return '127.0.0.1'
elif system() == "Windows":
try:
mac_address = get_mac_address().replace(":", "-").upper()
interface_temp = check_output('getmac /FO csv /NH /V', shell=False).decode('cp866').split("\r\n")
for face in interface_temp:
if mac_address in face:
return ifaddresses(face.split(",")[-1].replace('"', '').split("_")[-1]). \
setdefault(AF_INET)[0]['addr']
except Exception:
return '127.0.0.1'
Работа данной функции основана на работе двух библиотек. Одна позволяет получить в случае с Linux имя сетевого интерфейса по-умолчанию, то есть, библиотека getmac и передать полученное имя в функцию ifaddresses библиотеки netifaces. Она то и возвращает ip-адрес для сетевого интерфейса.
В случае с Windows, с помощью функции getmac получаем mac-адрес, который используется сетевым интерфейсом по-умолчанию. Приводим его в соответствии с тем, в каком виде он отображается в ОС Windows, то есть, меняем разделители.
Затем выполняем команду: getmac /FO csv /NH /V, выполняем ее парсинг, откуда, на основании сравнения полученного mac-адреса, забираем имя сетевого интерфейса. Если в случае с Linux имя может быть, для примера, eth0, то, в случае с Windows это имя будет заключено в фигурные скобки и выглядеть примерно так: {4D36E972-E325-11CE-BFC1-08002BE10318}. Именно это полученное имя сетевого интерфейса мы передаем в функцию ifaddresses библиотеки netifaces, откуда уже получаем ip-адрес сетевого интерфейса по-умолчанию.
На этом описание способов получения локального IP-адреса сетевого интерфейса по-умолчанию можно завершить. В принципе, описанных способов, должно хватить. Это не означает, что других способов не существует. Просто, если вам будет недостаточно описанного, можно сделать функцию самостоятельно, предварительно поискав в сети варианты получения адреса.
Получение IP-адреса шлюза по-умолчанию используемого в ОС
Иногда для использования в проектах нужно понимать, какой ip-адрес используется для шлюза в операционной системе. Для примера, при работе с библиотекой scapy. Для себя, данную задачу я решил в коде, который представлен ниже. Не скажу, что это универсальный способ. Он так же требует тестирования на различных дистрибутивах Linux и версиях ОС Windows. Также, в некоторых дистрибутивах нет модуля route, поэтому его нужно будет установить дополнительно. Для примера в Ubuntu его не было и пришлось устанавливать с помощью команды:
sudo apt install net-tools
Больше, кроме указанного модуля, установки сторонних библиотек не требуется. Скрипт получает данные выполняя команды ОС с помощью subprocess.check_output и забирает из них вывод, который впоследствии просто парситься.
Python:
"""
Возвращает ip-адрес роутера(шлюза) по-умолчанию на Linux и Windows машинах.
На некоторых linux-машинах требует установки пакета net-tools, т.к. без него
не работает команда route (пример: Ubuntu):
sudo apt install net-tools
"""
from ipaddress import IPv4Address
from socket import gethostbyname
from platform import system
from subprocess import check_output
def get_host(ip: str) -> str:
"""
Попытка получения ip-адреса по доменному
имени. В случае неудачи возвращает из
функции переданное в нее значение.
:param ip: доменное имя (или то, что было получено).
:return: ip-адрес или полученное в функцию значение.
"""
try:
sock = gethostbyname(ip)
return sock
except Exception:
return ip
def check_ip(ip: str) -> str:
"""
Проверка на принадлежность полученного
значения к ip-адресу. Если полученное значение
не является ip-адресом, возникает исключение и
полученное значение передается в функцию для получения
адреса по доменному имени.
Если же проверка проходит успешно, адрес возвращается
из функции.
:param ip: полученное значение (ip-адрес).
:return: ip-адрес или значение, которое возвращает функция get_host.
"""
try:
IPv4Address(ip)
return ip
except Exception:
return get_host(ip)
def router_ip() -> (str, None):
"""
Определяется версия ОС, затем, с помощью библиотеки subprocess выполняется
команда характерная для каждой из ОС, по определению адреса роутера.
Затем полученное значение отправляется на проверку соответствия адресу.
Если полученный адрес является IPv4, то он возвращается из функции.
В случае, если адрес является доменным именем, а такие случаи могут
быть, полученное имя передается в функцию, где выполняется попытка
получения ip-адреса по доменному имени. Для примера, если в системе
используется pfsence.
:return: возвращает IP-адрес роутера или маршрутизатора, в случае исключения
возвращает None.
"""
if system() == "Linux":
try:
ip_route = str(check_output('route -n | grep UG', shell=True).decode().split()[1])
return check_ip(ip_route)
except Exception:
return
elif system() == "Windows":
try:
ip_route = check_output('route PRINT 0* -4 | findstr 0.0.0.0', shell=True).decode('cp866'). \
split()[-3]
return check_ip(ip_route)
except Exception:
return
Также в данный код были добавлены дополнительные функции для проверки на соответствие полученного результата ip-адресу. Так как бывает, что возвращается не адрес, а доменное имя. У меня так было когда я использовал pfsence в качестве шлюза. Если проверка на ip-адрес не проходит, пробуем получить адрес по доменному имени. В случае же неудачи возвращаем то, что и получили изначально.
Получение сводной информации по сетевому интерфейсу используемому по-умолчанию
На этом получение разного рода адресов локальных сетевых интерфейсов можно завершить. И подытожить небольшой функцией, которая возвращает сводную информацию по сетевому интерфейсу, а именно: его имя, ip-адреса v4 и v6, адрес шлюза, а также mac-адрес.
Для этой цели я создал небольшую функцию, которая для работы не требует установки сторонних библиотек, а использует парсинг команд операционной системы. В связи с этим, эта функция также требует тестирования.
Python:
"""
Скрипт возвращает результат парсинга выполнения стандартных
команд Linux и Windows. Возвращается словарь с базовыми
параметрами сетевого интерфейса используемого по-умолчанию.
К базовым параметрам здесь относятся: имя сетевого интерфейса,
локальные ip-адреса v4 и v6, адрес шлюза по-умолчанию,
mac-адрес сетевого интерфейса.
На некоторых linux-машинах требует установки пакета net-tools,
т.к. без него не работает команда route (пример: Ubuntu):
sudo apt install net-tools
Кроме вышеуказанного пакета, сторонних библиотек
для работы скрипта не требуется.
"""
from ipaddress import IPv4Address
from socket import gethostbyname
from platform import system
from subprocess import check_output
def network_param() -> dict:
"""
Получаем базовые параметры сети, такие как:
- имя сетевого интерфейса по-умолчанию;
- адреса: IPv4 и IPv6;
- адрес шлюза по-умолчанию;
- mac-адрес сетевого интерфейса по-умолчанию.
:return: словарь с вышеуказанными параметрами.
"""
if system() == "Linux":
try:
com_run = check_output('ip -h -br a | grep UP', shell=True).decode()
default_interface = com_run.split()[0].strip()
ipv4 = com_run.split()[2].strip().split("/")[0]
ipv6 = com_run.split()[3].strip().split("/")[0]
except Exception:
com_run, default_interface, ipv4, ipv6 = None, None, None, None
try:
router_ip = str(check_output('route -n | grep UG', shell=True).decode().split()[1])
try:
IPv4Address(router_ip)
except Exception:
pass
else:
try:
ip = gethostbyname(router_ip)
router_ip = ip
except Exception:
pass
except Exception:
router_ip = None
try:
mac_address = check_output(f'ifconfig {default_interface} | grep "ether"', shell=True).decode(). \
split()[1]
except Exception:
mac_address = None
return {"Default Interface": default_interface, "IPv4 address": ipv4, "IPv6 address": ipv6,
"Router ip-address": router_ip, "MAC-address": mac_address}
elif system() == "Windows":
try:
net_set = check_output(
"wmic nicconfig get IPAddress, MACAddress, IPEnabled, SettingID, DefaultIPGateway /value"). \
decode("cp866"). strip().split("\r\r\n")
default_interface, ipv4, ipv6, router_ip, mac_address = None, None, None, None, None
text = ""
for net in net_set:
if net.strip() == "":
text = f"{text}|"
else:
text = f"{text}~{net.strip()}"
text = text.strip().split("||")
for tx in text:
if tx.split("~")[-3].split("=")[1] != "TRUE":
continue
for item in tx.split("~"):
if item.strip() == "":
continue
if item.strip().split("=")[0] == "IPEnabled":
continue
if item.strip().split("=")[1] != "":
if item.strip().split("=")[0] == "SettingID":
default_interface = item.strip().split("=")[1]
if item.strip().split("=")[0] == "DefaultIPGateway":
router_ip = item.strip().split("=")[1].replace("{", "").replace("}", "").replace('"', '')
if item.strip().split("=")[0] == "MACAddress":
mac_address = item.strip().split("=")[1]
if item.strip().split("=")[0] == "IPAddress":
ipv4 = item.strip().split("=")[1].split(",")[0].replace('"', '').replace("{", "")
ipv6 = item.strip().split("=")[1].split(",")[1].replace('"', '').replace("}", "")
except Exception:
default_interface, ipv4, ipv6, router_ip, mac_address = None, None, None, None, None
return {"Default Interface": default_interface, "IPv4 address": ipv4, "IPv6 address": ipv6,
"Router ip-address": router_ip, "MAC-address": mac_address}
Думаю, что описывать полный процесс парсинга здесь не имеет особого смысла. Вам достаточно получить вывод используемой команды и попытаться распарсить его самостоятельно. Вполне возможно, что это у вас получиться более изящным способом. У меня это на данный момент выглядит так, как представлено в коде, но, тем не менее — работает.
Получение внешнего(публичного) IP-адреса вашей сети
Также как и локальный IP-адрес, внешний IP довольно часто используется в самых разнообразных проектах. И потому, его получение это довольно важная задача. Впрочем, решается она довольно просто. Выполняется запрос к api сервиса и забирается ответ в виде внешнего ip-адреса. Я использовал здесь
Ссылка скрыта от гостей
, однако, сервисов подобного рода довольно много. Просто это — один из них. Для работы данного скрипта потребуется установка библиотеки requests, так как именно с ее помощью мы будем выполнять запросы и получать ответ. Устанавливается она с помощью команды:pip install requests
Теперь же, давайте перейдем непосредственно к коду:
Python:
"""
Скрипт получает внешний ip-адрес компьютера, с которого
отправляется запрос.
Возможно использовать для тестирования proxy.
Для работы требует установки библиотеки requests:
pip install requests
"""
from requests import get
def public_ip() -> str:
"""
Получение внешнего ip-адреса путем обращения к api сервиса:
https://api.ipify.org/
:return: возвращает полученный внешний ip-адрес.
"""
try:
return get('https://api.ipify.org/').text
except Exception:
return '127.0.0.1'
Выполняем запрос к api сервиса, получаем ответ в виде текста и возвращаем его из функции. Здесь не требуется парсить возвращаемый код, так как данные прилетают уже в текстовом виде. Ну и обернем все это в блок try - except, который будет отрабатывать в случае возникновения исключений. Как вариант, если у вас отсутствует интернет, будет вызвано исключение и вам просто вернется адрес локальной петли: 127.0.0.1.
Получение ip-адреса домена по его имени, имени домена по ip-адресу, а также названий сервисов работающих на определенном порту
Код, который выполняет получение всех данных, которые описаны в заголовке я решил объединить в один блок, так все функции в нем используют те или иные методы библиотеки socket. На самом деле, все это достаточно просто найти в интернете, но, когда это под рукой, да еще и с реализованной проверкой, становиться гораздо проще использовать уже готовый код, а не писать каждый раз все с нуля.
Давайте начнем с функции, которая получает ip-адрес домена.
Python:
"""
Возвращает ip-адрес домена, если таковой удается получить.
Не требует для работы сторонних библиотек. Принимает в
качестве параметров строку с адресом, доменом или ip.
Установки сторонних библиотек не требуется.
"""
from ipaddress import IPv4Address
from socket import gethostbyname, gaierror
def ip_from_domain(domain: str) -> (str, None):
"""
Производиться обработка поступившего от пользователя адреса.
Удаление http(s) для дальнейшего получения ip домена.
Запрашивается ip-адрес, который возвращается из функции,
если его удалось получить. Если нет, возвращается None.
:return: возвращает ip-адрес или None, если его не удалось получить.
"""
try:
IPv4Address(domain)
return domain
except Exception:
if domain.startswith("http"):
domain = domain.split("/")[2]
if len(domain.split(".")) > 2:
domain = ".".join(domain.split(".")[1:])
try:
ip_domain = gethostbyname(domain)
return ip_domain
except gaierror:
return
Как видите, используется метод библиотеки socket — gethostbyname. Но, перед тем как получить ip-адрес, необходимо выполнить проверку, не является ли переданное имя домена, собственно ip-адресом. Такое иногда случается. Выполняем данную проверку с помощью функции IPv4Address библиотеки ipaddress. Если переданное в функцию значение является ip-адресом, мы просто возвращаем его из функции, так как выполнять получение того, что уже получено не имеет смысла.
Если же переданное значение ip-адресом не является, возникает исключение и мы проверяем домен на наличие в нем протоколов и прочих путей, которые в данном контексте излишни. Производим очистку и на всякий случай проверяем, не является ли домен, полученный после очистки субдоменом. Если да, в этом случае забираем из него домен и уже полученное после очистки имя домена передаем дальше. Ну и в случае успешного получения ip-адреса возвращаем его из функции, если же возникло исключение, возвращаем None.
Следующая функция — получение домена по ip-адресу. Работает корректно не всегда, зачастую возвращаются PTR записи в формате arpa.
Python:
"""
Возвращает доменное имя в случае его нахождения,
или ptr запись arpa.
Для работы установки сторонних библиотек не требуется.
"""
from ipaddress import IPv4Address
from _socket import gethostbyaddr
def domain_ip(ip) -> (str, None):
"""
Получение домена по ip-адресу.
Работает не всегда корректно. Зачастую возвращается
reverse dns.
:return: домен или False в случае неудачи.
"""
try:
IPv4Address(ip)
except Exception:
return
try:
return gethostbyaddr(ip)[0]
except Exception:
return
В данном коде используется метод gethostbyaddr библиотеки socket. Здесь также выполняем проверку на принадлежность полученных данных к ip-адресу, если это не ip-адрес, просто выходим из функции. Если же адрес корректен, выполняем попытку получения имени домена. Ну и также обрабатываем исключение, которое может возникнуть при получении имени домена.
Ну и последняя функция в данном блоке, это функция получения имени сервиса, который работает на определенном порту. Может быть полезна, когда вы выполняете сканирование портов, для получения имени сервиса работающего на открытом потру.
Python:
"""
Скрипт получения имени сервиса работающего
на определенном порту. В случае неудачи возвращается
значение Unknown.
Установки дополнительных библиотек для работы не требует.
"""
from socket import getservbyport
def serv_on_port(port: int) -> str:
"""
Попытка получения сервиса который работает на определенном порту.
Если получить название сервиса не удается, вызывается исключение
и возвращается значение Unknown.
:param port: номер порта (целочисленное значение).
:return: строка, сервис на данном потру или Unknown в случае неудачи.
"""
try:
return getservbyport(port)
except Exception:
return "unknown"
def type_port(port) -> str:
"""
Проверяет тип полученного значения.
Если это целое число, то передаем в
функцию получения сервиса без изменений.
Если это строка, проверяем, является ли
эта строка числом. Если да, переводим в
int и передаем в функцию получения сервиса.
:param port: номер порта (строковое или целочисленное значение).
:return: сервис порта или Unknown, в случае неудачи получения.
"""
if type(port) == int:
return serv_on_port(port)
elif type(port) == str:
if port.isdigit():
return serv_on_port(int(port))
В данном коде выполняется попытка получения имени сервиса с помощью метода getservbyport библиотеки socket. В случае неудачи возвращаем unknown, так как возвращаются далеко не все имена портов. Ну и здесь добавлена функция проверки типа полученных данных. То есть, кто-то может передать порт как целое число, а кто-то как простую строку. Ну мало ли, всякое бывает. Вот тут и проверяется тип данных. И выполняется попытка привести данные к нужному виду для передачу в функцию.
То есть, в данном случае использовать данный код нужно как-то так:
print(type_port(80))
Ну или можно добавить предварительный вызов функции непосредственно в функции serv_on_port. Тут уже по вашему желанию.
А на этом с портами, доменами и их ip-адресами можно закончить.
Пинг домена или IP-адреса
Да, здесь пойдет речь о простом пинге доменов и адресов. Можно конечно использовать subprocess и распарсить его вывод, но зачем изобретать велосипед, если для этого есть удобная библиотека. Для работы данного кода требуется установка библиотеки ping3:
pip install ping3
Нужно иметь в виду, что это простой пинг и о высокой точности обнаружения работоспособного домена ожидать здесь не стоит, так как некоторые домены, особенно те, которые находятся за CDN, просто ничего не возвращают, так как у них стоит запрет пинга. Для примера можете выполнить пинг на адрес codeby.net и вы увидите, что все отправленные пакеты были благополучно потеряны. А все потому, что данный домен находиться за CDN ddos-guard.net. Хотя, на самом деле странно. В информации whois все еще значиться cloudflare. Но, думаю, что это просто хитрый ход админов )). Вот, сам код:
Python:
"""
С помощью данного скрипта выполняется стандартный ping
определенного адреса или доменного имени. Работает так же,
как и стандартный модуль ОС. Не пингует адреса с защитой от
пинга, такие, как, для примера, "codeby.net".
Возможно использование для неглубокой проверки адресов.
Для работы требует установки библиотеки:
pip install ping3
"""
from ipaddress import IPv4Address
from ping3 import ping
def ping_addr(addr: str) -> bool:
"""
Обычный пинг определенного ip-адреса или домена.
Оригинальная функция возвращает в случае недоступности
домена или адреса None. В случае успеха, время, за которое
выполняется ping.
В данной функции возвращаются булевы значения в случае
успеха или неудачи.
:param addr: ip-адрес или домен (без http(s)).
:return: True или False в зависимости от результата ping.
"""
try:
IPv4Address(addr)
except Exception:
if addr.startswith("http"):
addr = addr.split("/")[2]
if len(addr.split(".")) > 2:
addr = ".".join(addr.split(".")[1:])
try:
if ping(addr) is not None:
return True
return False
except Exception:
return False
Как видите, здесь также не происходит ничего особого. Для начала выполняется проверка, что же мы получили на вход функции. Если ip — пропускаем, если домен, пытаемся обработать, прежде чем отправить в функцию пинга. Ну и, собственно, сам пинг, с помощью модуля ping. Если пинг адреса прошел удачно, возвращается время, за которое был получен ответ. Если же нет, возвращается None. В зависимости от этого возвращаем True или False из функции.
Получение координат ip-адреса
На самом деле не особо полезная функция, так как выдает достаточно приблизительный результат. Но, тем не менее использовать ее в работе можно, хоть и с очень большими погрешностями. Для примера, если я попытаюсь определить координаты моего ip, я получу в ответ широту и долготу, которая удалена от меня, примерно на полторы тысячи километров. Тем не менее, поехали. Для работы данного скрипта потребуется установка библиотеки requests:
pip install requests
С ее помощью мы будем выполнять запросы к api сервиса
Ссылка скрыта от гостей
и получать в ответ json с данными, в котором помимо координат содержится еще и адрес. Но, в данном контексте он нам не нужен. Поэтому, забираем только широту и долготу и возвращаем в формате списка.
Python:
"""
Сервис возвращает координаты по переданному ip-адресу.
Для работы требует установки библиотеки:
pip install requests
"""
from requests import get
def geo_ip(ip) -> (list, bool):
"""
Функция для получения координат по внешнему IP-адресу.
Выполняет обращение к сервису, в который передает адрес.
В ответ получает JSON с координатами и не только (остальные данные не возвращаются из функции).
Однако, при необходимости возможно ее расширение.
:return: возвращает широту и долготу по IP.
"""
try:
req = get(url=f'http://ip-api.com/json/{ip}').json()
return [req['lat'], req['lon']]
except Exception:
return
Вы можете возвращать это как-то по-другому. Мне нужно было возвращать координаты в таком виде, когда я использовал их в другой функции, которая будет представлена ниже. А в нее передавать координаты необходимо именно списком. Потому, так получилось. Ну я так и оставил.
Получение физического адреса по координатам
Для работы данного скрипта потребуется установка библиотеки geopy:
pip install geopy
В функцию представленную ниже передаются координаты объекта, адрес которых требуется получить. Данные передаются в виде списка, который представляет из себя широту и долготу. Также для переменной location предусмотрено значение по-умолчанию, для того, чтобы исключить ошибки в работе. Поэтому, проверяем, что содержится в данной переменной и уж только потом передаем данные в модуль Nominatim. Указываем user_agent. Здесь он на самом деле может быть любой. Затем указываем, что хотим получить обратную геолокацию с помощью функции reverse, и с забираем из полученных данных адрес. Если же возникает исключение, возвращаем None. В работе данной библиотеки используются данные OpenStreetMap, а потому, некоторые адреса получить не представляется возможным. Но, это скорее исключение, чем правило. В основном библиотека отрабатывает на пять баллов.
Python:
"""
Возвращает адрес по переданным координатам.
Координаты необходимо передать в виде списка.
То есть, учитывать возможность передачи пользователем
в скрипт в виде списка через запятую, но для отправки в функцию
обязательно формировать список.
Для работы скрипта необходима установка библиотеки:
pip install geopy
"""
from geopy.geocoders import Nominatim
def get_addr(location=None) -> (str, bool):
"""
Получение адреса по координатам (обратная геолокация).
:param location: координаты (широта и долгота).
:return: адрес локации.
"""
if location is None:
return
try:
return Nominatim(user_agent="GetLoc").reverse(location).address
except Exception:
return
Вот, собственно, те функции, которыми я хотел с вами поделиться сегодня. Наверняка их список далеко не полный и его можно расширять. Но, о других данных попробуем поговорить уже в другой статье. Ну и да, я для теста все файлы с функциями скидал в отдельную папочку, которую обозначил как модуль. Таким образом, для использования в своем коде я могу либо вызвать функцию из модуля, либо просто скопировать файл с функцией к себе в папку с кодом. Либо скопировать сам код и вставить в нужный скрипт.
Сделал небольшой скрипт для теста, который вызывает по очереди все выше представленные функции.
Python:
import network_base.ipv4_local_cli as ipv4_cli
import network_base.ipv4_local_getmac as ipv4_gm
import network_base.ipv4_local_sock as ipv4_sock
import network_base.router_ip as getaway
import network_base.network_params as net_param
import network_base.ip_from_domain as ip_domain
import network_base.domain_from_ip as domain_ip
import network_base.service_on_port as serv_port
import network_base.my_public_ip as public_ip
import network_base.ping_address as ping_addr
import network_base.geolocation_ip as geo_ip
import network_base.addr_from_geo as addr_geo
print(f'Локальный IP: {ipv4_cli.local_ipv4()}')
print(f'Локальный IP: {ipv4_gm.local_ipv4()}')
print(f'Локальный IP: {ipv4_sock.local_ipv4()}')
print("")
print(f'IP шлюза по-умолчанию: {getaway.router_ip()}')
print("")
print(f'Параметры сетевого интерфейса по-умолчанию:\n{net_param.network_param()}')
print("")
print(f"IP-адрес домена codeby.net: {ip_domain.ip_from_domain('codeby.net')}")
print(f"Доменное имя codeby.net по полученному IP: {domain_ip.domain_ip(ip_domain.ip_from_domain('codeby.net'))}")
print(f'Имя службы работающей на порту 80: {serv_port.type_port(80)}')
print("")
print(f'Ваш публичный IP: {public_ip.public_ip()}')
print(f'Пинг домена или адреса: {ping_addr.ping_addr("185.178.208.177")}')
print(f"Координаты по IP-адресу: {geo_ip.geo_ip(domain_ip.domain_ip(ip_domain.ip_from_domain('codeby.net')))}")
print(f"Физический адрес по координатам: "
f"{addr_geo.get_addr(geo_ip.geo_ip(domain_ip.domain_ip(ip_domain.ip_from_domain('codeby.net'))))}")
Особо с кодом не заморачивался, так как он служит только лишь для демонстрации работы. Ну и вывод функции на виртуалке с Windows:
Ну и на реальной машине с Linux Mint. Некоторые данные пришлось заблюрить:
А на этом все.
Код описанный в статье доступен во вложении.
Спасибо за внимание. Надеюсь информация будет вам полезна
Вложения
Последнее редактирование модератором: