Найти в Дзене

Как получить IP локального хоста через C#/.NET без сторонних инструментов.

Привет! Так получилось, что я пишу на C# микросервис, отвечающий за рассылку писем. В процессе работы моего сервиса может случится что-то плохое, и нужно сделать рассылку. А для того, чтобы она была более информативная и господа из DevOps были менее раздражены, то в письмо следует добавить IP-адрес, где именно что-то упало. Но вот проблема - В .NET нет нормального, простого метода или свойства в библиотеке System.Net для простого получения IP-адреса текущего хоста. И тут пришлось начать писать свои «костыли»... По ходу прочтения поста у Вас возникнут вопросы, почему так, а не иначе. На них я постарался ответить в конце. Попытка 1: Окей. Пойдем решать задачу самым очевидным путем, через Google.
Вписываем : Получить ip локального хоста C#. Получаем список ответов, среди результатов есть документация Microsoft на метод Dns.GetHostEntry, которая намекает по названию, что за нас все давно придумали. Смотрим документацию и в ходе недолгих поисков находим нужные методы получения IP-адрес
Оглавление

Привет!

Так получилось, что я пишу на C# микросервис, отвечающий за рассылку писем.

В процессе работы моего сервиса может случится что-то плохое, и нужно сделать рассылку. А для того, чтобы она была более информативная и господа из DevOps были менее раздражены, то в письмо следует добавить IP-адрес, где именно что-то упало.

Но вот проблема - В .NET нет нормального, простого метода или свойства в библиотеке System.Net для простого получения IP-адреса текущего хоста. И тут пришлось начать писать свои «костыли»...

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

Попытка 1:

Окей. Пойдем решать задачу самым очевидным путем, через Google.
Вписываем : Получить ip локального хоста C#.

Получаем список ответов, среди результатов есть документация Microsoft на метод Dns.GetHostEntry, которая намекает по названию, что за нас все давно придумали.

Смотрим документацию и в ходе недолгих поисков находим нужные методы получения IP-адреса указанного хоста:
-
GetHostAddresses(string) - для получения списка IP с указанного хоста.
-
GetHostName() - для возвращает имя узла локального хоста, где будет запущен микросервис.

Супер! Запускам тест в консоли и получаем:

Получаем список всех IP адресов на интерфейсах.
Получаем список всех IP адресов на интерфейсах.

Почти идеально, но нам не нужны IPv6 адреса.

В документации для этого есть перегрузка метода
GetHostAddresses(String, AddressFamily), которая поможет отсечь ненужные IPv6 адреса интерфейсов.

Проваливаемся в
AddressFamily и видим enum, благодаря которой мы можем сделать фильтр по схеме адресации.

Необходимый enum с OPv4-адресами
Необходимый enum с OPv4-адресами

Отлично! Оно нам и нужно.
Добавляем и запускаем консоль еще раз.

Результат ошеломляет своей простотой =)
Результат ошеломляет своей простотой =)

Идем в сетевые настройки на своем устройстве и сверяем c полученным IP в списке консоли.

IP на нужном интерфейсе. В моем случае это wi-fi.
IP на нужном интерфейсе. В моем случае это wi-fi.

Вот решение, вот наш IP в списке. Осталось сделать в массиве testResult сделать выбор второго значения (testResult[1]) и получить наш IP хоста.
Но увы нет...

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

Вернемся к полученному списку IP-адресов.
Как мы видим первый вообще IP
192.168.56.1. Что это за IP? Почему он первый? И вообще, что за другие IP адреса у меня?

-6

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

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

Get-NetIPAddress | Format-Table

Рекомендую пользоваться Powershell чаще. Это мощная штука, особенно если вы .NET разработчик.
Рекомендую пользоваться Powershell чаще. Это мощная штука, особенно если вы .NET разработчик.

Тут мы и видим все IP из списка.
А теперь глядим на префикс
PrefixOrigin. Он показывает, как назначается IP в сетевом интерфейсе.
Получается Wi-Fi интерфейсу IP назначается по
DHCP, а остальным - в ручную (Manual).

Давайте ради любопытства глянем, что это за интерфейс назначен на IP 192.168.56.1.

ipconfig /all

-8

Ага, понятно - это интерфейс VirtualBox. Менять его, удалять и т.д. не в ходит в наш план и задачу.


Делаем промежуточные выводы:


1. Хост, на котором будет установлен микросервис и с которого будет браться информация, может содержать тучу виртуальных сетевых интерфейсов (адаптеров), либо вообще один единственный.

2. Судя по всему список формируется как-то рандомно и не понятно мне. Если обратить внимание на полученную таблицу IP-адресов в Powershell, формирование списка игнорирует возрастание по столбцу
ifIndex.
Мы получаем рандом почти что.

3. А как такой список формируется на сервере и на других компах например? Было бы классно это выяснить.

Обратившись к коллегам, я получил результаты как это работает на другом компьютере и на Windows сервере, где вероятно будет запущен мой микросервис.

На другом компьютере результат получился почти как у меня:

Результат команды Powershell: Get-NetIPAddress | Format-Table -Property ifIndex,IPAddress, PrefixOrigin
Результат команды Powershell: Get-NetIPAddress | Format-Table -Property ifIndex,IPAddress, PrefixOrigin
Результат запуска нашего кода в VS.
Результат запуска нашего кода в VS.

Windows Server показал вот такие результаты:

-11
Резюмируем:
Наш код вероятно не покажет актуальный IP.
Как делать выборку - не понятно. Каждый хост - это уникальное кол-во сетевых интерфейсов.
Список вообще формируется не по индексу.

Попытка 2:

Из прошлой попытки я понял, что нужно самому филировать IP по полю PrefixOrigin, которое мы получили в Powershell.

Получить результат можно командлетом: Get-NetIPAddress | Format-Table -Property ifIndex,IPAddress, PrefixOrigin
Получить результат можно командлетом: Get-NetIPAddress | Format-Table -Property ifIndex,IPAddress, PrefixOrigin

Но вот нюанс в том, что из класса Dns и его методов\свойств, не дотянуться до нужного значения.
Идея пришла следующая: если Powershell может провернуть эту операцию используя тоже платформу .NET, то нам просто нужно найти как дотянуться до необходимой информации через C#.

В ходе часового поиска я нашел нужный класс
NetworkInterface.
В классе нам нужно будет использовать метод
GetIPProperties(), свойство UnicastAddresses, Address, а так же enum другого класса NetworkInformation -> System.Net.NetworkInformation.PrefixOrigin

Теперь код без подробностей:

// формируем массив с интерфейсами
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();

// Создаем список с нашими Ip адресами
List<string> list = new List<string>();
// Перебираем сетевые интерфейсы
foreach (NetworkInterface nic in nics)
{
// фильтруем сетевые интерфейсы по префиксу DHCP который нам и нужен
if (nic.GetIPProperties().UnicastAddresses.LastOrDefault()?.PrefixOrigin == System.Net.NetworkInformation.PrefixOrigin.Dhcp)
{
// Узнаем IP в нужного интерфейса и записываем в List.
IPAddress? res = nic.GetIPProperties().UnicastAddresses.LastOrDefault()?.Address;
list.Add(res.ToString());
}
}

// Второй волной заполняем IP с префиксом Manual
foreach (NetworkInterface nic in nics)
{
if (nic.GetIPProperties().UnicastAddresses.LastOrDefault()?.PrefixOrigin == System.Net.NetworkInformation.PrefixOrigin.Manual)
{
IPAddress? res = nic.GetIPProperties().UnicastAddresses.LastOrDefault()?.Address;
list.Add(res.ToString());
}
}


Подробности кода:

Получение списка сетевых интерфейсов:

NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();

Когда мы используем свойство UnicastAddresses, то получаем list из двух значений:
1-
Ipv6 адрес интерфейса.
2-
Ipv4 адрес интерфейса.

Назначение таких IP-адресов идет именно по Unicast. В Broadcast и AnyCast Вы не найдете нужного вам значения.
Вкратце об этом
тут.


Нас интересует второй из них. Но что бы избавиться от "магических" чисел мы прикрутили
.LastOrDefault(), который выберет последнее значение.

Даже если отключить в сетевом интерфейсе IPv6 и получаемое значение останется одно в List, то мы все равно получим нужный IP благодаря .LastOrDefault()
Вот пример почему в данном случае использовать .UnicastAddresses[1].PrefixOrigin было бы плохой идеей.


PrefixOrigin поможет нам в сравнении с NetworkInformation.PrefixOrigin.Dhcp, отфильтровать нужные IP.

nic.GetIPProperties().UnicastAddresses.LastOrDefault()?.PrefixOrigin

Значения enum System.Net.NetworkInformation.PrefixOrigin.Dhcp
Значения enum System.Net.NetworkInformation.PrefixOrigin.Dhcp


Если префикс подошел, то меняем дальше по коду свойство
PrefixOrigin на Address для получения IP-адреса интерфейса и записываем результат.

IPAddress? res = nic.GetIPProperties().UnicastAddresses.LastOrDefault()?.Address;

Результат:


Мы получаем, благодаря написанному методу и использованию класса
NetworkInterface, нужный результат, который еще и сам сортируется по возрастанию индекса интерфейса (ifIndex), который мы наблюдали в командлете Powershell.

Теперь результат вот такой
Теперь результат вот такой

Теперь мы можем с большой вероятностью достать нужный ip первым в списке и использовать его.

Конец.


P.S:

  1. Двойной foreach сделан мной для удобства понимания кода, тут нет колоссальной работы ресурсов хоста при такой работе.
  2. Список в начале наполняет DHCP адресами, но если таких нет, как на примере скрина с Window Server, то список наполнится адресами с PrefixOrigin = Manual и вероятно первый же из них будет верным.
  3. Данная реализация сядет в лужу, если на сервере две и более сетевые платы.
  4. Проблема произойдет, если на сервере накручены хитрые сетевые настройки, где IP получается по DHCP одним их сетевых интерфейсов, а нужный интерфейс, вообще в Manual почему-то.
    Всякие сетевые извращения - это ваши трудности =)
  5. Код не покажет актуальный IP, если устройство находится за роутером. Да, прилетит локальный IP, а не белый IP с границы вашей сети. Обратите на это внимание.
  6. Данный код рассчитан под выделенные сервера на Azure или YandexCloud и т.д. имеющие белый IP-адрес на интерфейсе. Но не под пункт 3,4,5.
  7. Почему не интегрировать просто zabbix к работе сервиса? У нас не стоит такой задачи в рамках реализации MVP.
    Скажу более: этот проект вероятно и не уйдет далее.
  8. А почему бы не достать IP хоста с какой-то файла в системе?
    Отличное решение. Если бы я знал, где и как это провернуть, то обязательно сделал. Добро пожаловать в комментарии с решением)
  9. В проекте используется C# 10 и .NET 6.