Салимóненко Дмитрий Александрович

Вычислительные Сети, Системы и Телеком ­муникации

ЗАДАНИЕ 2: Получение информации о сетевых интерфейсах LINUX




I. Введение (теория)

↑ К содержанию ↑

Информация о сетевых интерфейсах в любой операционной системе (в том числе и в Linux) бывает необходим для того, чтобы, например:

  • узнать имя сетевого интерфейса,
  • обратиться к сетевому интерфейсу в ситуации, когда неизвестно его имя и/или IP-адрес,
  • узнать IP-адрес (публичный или приватный).

Для работы с сетями, в том числе и в ОС Linux, на самом деле, существует множество различных средств, в частности – структур. Это, в первую очередь:

struct sockaddr,
struct sockaddr_in,
struct ifreq,
struct if_nameindex,
struct ifparam,
struct in_addr,
struct if_nameindex

и т.д.

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

Примечание. Помимо интерфейса сокетов существует немало и других интерфейсов. Например, транспортный интерфейс TLI, вызов удаленной процедуры RPC и др.

Интерфейс сокетов основан на использовании, как правило, транспортных протоколов ТСР/UDP. Тогда как программа, основанная на TLI, может быть перенесена и на другие виды транспортных протоколов.

Вместе с тем, интерфейс сокетов является на данный момент прочно устоявшейся а, главное, удобной концепцией и технологией.

Интерфейс сокетов впервые появился в BSD Unix. Программный интерфейс сокетов описан в стандарте POSIX.1 и в той или иной мере поддерживается всеми современными операционными системами. В настоящее время – это настолько универсальное и мощное средство, что целесообразно использовать именно его (хотя бы с учетом легкости переносимости программ из одной ОС в другую) в противовес иным, быть может, с какой-то точки зрения более удобным и/или эффективным интерфейсам.

Что такое сокет, как он создается, какие они бывают и т.д. – можно прочитать в наших Методических указаниях по предмету Вычислительные Системы, Сети и Телекоммуникации (ВССТ), а также здесь. На данный момент будем считать, что читатель знаком с сокетами. Если нет, то, пожалуйста, ознакомьтесь - прежде, чем продолжить дальнейшее чтение этого материала.

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

Примечание. Все три листинга, скорее всего, потребуют запуска в привилегированном режиме, т.е. с правами администратора (root). Например, в Ubuntu это осуществляется следующим образом (пример):

sudo  ./a.out

затем указывается пароль.

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


2. Структура ifreq

↑ К содержанию ↑

Это – системная структура, содержащая параметры функции ioctl для работы с устройствами (о самой функции – см. ниже):

struct ifreq {
 char ifr_name[IFNAMSIZ]; /* имя интерфейса */
 union {
 struct sockaddr ifr_addr;
 struct sockaddr ifr_dstaddr;
 struct sockaddr ifr_broadaddr;
 struct sockaddr ifr_netmask;
 struct sockaddr ifr_hwaddr;
 short     ifr_flags;
 int     ifr_ifindex;
 int     ifr_metric;
 int     ifr_mtu;
 struct ifmap  ifr_map;
 char    ifr_slave[IFNAMSIZ];
 char    ifr_newname[IFNAMSIZ];
 char     *ifr_data;
 };
;

ifr_addr      – IP адрес интерфейса
ifr_dstaddr     – адрес сервера (для Point-to-Point соединения)
ifr_broadaddr   – широковещательный адрес интерфейса
ifr_netmask     – маска подсети
ifr_hwaddr    – mac адрес
ifr_ifindex     – индекс интерфейса (внутри ядра сетевые интерфейсы имеют уникальные индексы, для упращения работы сетевой подсистемы)
ifr_flags         – различные флаги (интерфейс поднят или опущен, интерфейс активен или неактивен и др.)
ifr_metric    – метрика интерфейса
ifr_mtu       – mtu интерфейса
ifr_map      – структура, содержащая в себе техническую информацию (номер прерывания, память устройства и т.д.)
ifr_slave     – подчиненное устройство
ifr_newname   – новое имя интерфейса (для переименования)

Примечание. Перед любым использованием структуры следует ее обязательно обнулять с помощью функции memset, а затем задавать имя интересуемого интерфейса ifr_name.

Обычно, программист указывает имя нужного устройства в ifr_name. Все остальные поля структуры могут занимать единое пространство в памяти.

О параметре IFNAMSIZ можно прочитать в здесь.

3. Функция ioctl

(Input-Output Control)
↑ К содержанию ↑

<sys/ioctl.h>
nt ioctl(int d, int request, …);

d – это открытый файловый дескриптор устройства (например, дескриптор сокета).

request – это тип запроса, для различных устройств запросы различные.

Пример применения функции:

ioctl(fd, SIOCGIFADDR, &ifr)

fd – дескриптор сокета,

SIOCGIFADDR – тип запроса «Назначение адреса устройства» (см. ниже),

&ifr – ссылка на структуру  struct ifreq ifr; .


Это – функция для работы с устройствами (низкоуровневая). Точнее, конечно, для работы – с драйверами устройств. Вызов ioctl считается привилегированным, то для его использования необходимо иметь эффективный идентификатор пользователя 0 или мандат CAP_NET_ADMIN. В противном случае будет возвращено значение EPERM.

SIOCGIFNAME

Возвращает в ifr_name имя интерфейса для заданного индекса ifr_ifindex. Это единственный вызов ioctl, возвращающий результат в ifr_name.

SIOCGIFINDEX

Возвращает индекс интерфейса в ifr_ifindex.

SIOCGIFFLAGS, SIOCSIFFLAGS

Считывает или устанавливает слово флагов устройства. В ifr_flags содержится битовая маска из следующих значений:

Флаги устройства Значения
IFF_UP Интерфейс активен.
IFF_BROADCAST Установлен правильный широковещательный адрес.
IFF_DEBUG Флаг внутренней отладки.
IFF_LOOPBACK Интерфейс является устройством обратной петли.
IFF_POINTOPOINT Интерфейс является соединением точка-точка.
IFF_RUNNING Ресурсы выделены.
IFF_NOARP Нет протокола arp, адрес назначения 2-ого уровня (L2) не установлен.
IFF_PROMISC Интерфейс в режиме захвата (promiscuous).
IFF_NOTRAILERS Избегать использования концевиков (trailers).
IFF_ALLMULTI Принимать все многоадресные пакеты.
IFF_MASTER Мастер в связке балансирования нагрузки.
IFF_SLAVE Подчинённый в связке балансирования нагрузки.
IFF_MULTICAST Поддержка многоадресной передачи.
IFF_PORTSEL Может выбирать тип среды с помощью ifmap.
IFF_AUTOMEDIA Запущен автоматический выбор среды.
IFF_DYNAMIC Адреса теряются, если интерфейс становится неактивным.
IFF_LOWER_UP Драйвер L1 указывает на включение интерфейса (начиная с Linux 2.6.17)
IFF_DORMANT Драйвер L1 указывает на неактивность интерфейса (начиная с Linux 2.6.17)
IFF_ECHO Посылать пакеты echo (начиная с Linux 2.6.25)

Установка флага в слове флагов является привилегированной операцией, но считывать его может любой процесс.

SIOCGIFPFLAGS, SIOCSIFPFLAGS

Считает или устанавливает (индивидуальные) флаги устройства. Значение ifr_flags представляет собой битовую маску следующих значений:

Индивидуальные флаги Значения
IFF_802_1Q_VLAN Интерфейс является устройством 802.1Q VLAN.
IFF_EBRIDGE Интерфейс является устройством моста Ethernet.
IFF_SLAVE_INACTIVE Интерфейс является неактивным подчинённым в связке балансирования нагрузки.
IFF_MASTER_8023AD Интерфейс является мастером в связке балансирования нагрузки 802.3ad.
IFF_MASTER_ALB Интерфейс является мастером связки балансирования нагрузки в режиме balanced-alb.
IFF_BONDING Интерфейс является мастером или подчинённым в связке балансирования нагрузки.
IFF_SLAVE_NEEDARP Интерфейсу требуется ARP для проверки.
IFF_ISATAP Интерфейс является интерфейсом RFC4214 ISATAP.

Установка расширенных (индивидуальных) флагов интерфейса является привилегированной операцией.

SIOCGIFADDR, SIOCSIFADDR

Назначает или получает адрес устройства с помощью ifr_addr. Назначение адреса интерфейса является привилегированной операцией. Для совместимости, принимаются или возвращаются только адреса семейства AF_INET.

SIOCGIFDSTADDR, SIOCSIFDSTADDR

Назначает или получает адрес назначения устройства точка-точка с помощью ifr_dstaddr. Для совместимости, принимаются или возвращаются только адреса семейства AF_INET. Присвоение адреса назначения является привилегированной операцией.

SIOCGIFBRDADDR, SIOCSIFBRDADDR

Назначает или получает широковещательный адрес устройства с помощью ifr_brdaddr. Для совместимости, принимаются или возвращаются только адреса семейства AF_INET. Присвоение широковещательного адреса является привилегированной операцией.

SIOCGIFNETMASK, SIOCSIFNETMASK

Назначает или получает маску сети устройства с помощью ifr_netmask. Для совместимости, принимаются или возвращаются только адреса семейства AF_INET. Назначение маски сети адреса является привилегированной операцией.

SIOCGIFMETRIC, SIOCSIFMETRIC

Считывает или устанавливает метрику устройства с помощью ifr_metric. В данный момент возможность не реализована; при считывании ifr_metric присваивается значение, равное нулю, а при попытке установки возвращается значение EOPNOTSUPP.

SIOCGIFMTU, SIOCSIFMTU

Считывает или устанавливает MTU (Maximum Transfer Unit — максимальную порцию данных) с помощью ifr_mtu. Установка MTU является привилегированной операцией. Установка слишком маленьких значений может привести к авариям в ядре.

SIOCGIFHWADDR, SIOCSIFHWADDR

Считывает или устанавливает аппаратный адрес устройства с помощью ifr_hwaddr. Аппаратный адрес задаётся в структуре sockaddr. В sa_family содержится тип устройства ARPHRD_*, в sa_data содержится аппаратный адрес L2, начиная с байта 0. Установка аппаратного адреса является привилегированной операцией.

SIOCSIFHWBROADCAST

Устанавливает аппаратный широковещательный адрес устройства с помощью ifr_hwaddr. Это привилегированная операция.

SIOCGIFMAP, SIOCSIFMAP

Считывает или устанавливает аппаратные параметры интерфейса с помощью ifr_map. Установка параметров является привилегированной операцией.

struct ifmap {
 unsigned long   mem_start;
 unsigned long   mem_end;
 unsigned short  base_addr;
 unsigned char   irq;
 unsigned char   dma;
 unsigned char   port;
};

Назначение структуры ifmap зависит от драйвера устройства и архитектуры системы.

SIOCADDMULTI, SIOCDELMULTI

Добавляет или удаляет адрес из фильтров многоадресной передачи уровня связи устройства с помощью ifr_hwaddr. Это привилегированная операция. Альтернативный способ приведен в packet(7).

SIOCGIFTXQLEN, SIOCSIFTXQLEN

Считывает или устанавливает размер очереди передачи устройства с помощью ifr_qlen. Установка размера очереди передачи является привилегированной операцией.

SIOCSIFNAME

Изменяет имя интерфейса, указанное в ifr_name, на ifr_newname. Это привилегированная операция. Она разрешена только тогда, когда интерфейс не активен.

SIOCGIFCONF

Возвращает список адресов (транспортного уровня) интерфейсов. В настоящее время, сюда входят только адреса семейства AF_INET (IPv4), для совместимости. В отличии от других, данный ioctl передаёт структуру ifconf:

struct ifconf {
 int     ifc_len; /* размер буфера */
 union {
 char     *ifc_buf; /* адрес буфера */
 struct ifreq   *ifc_req; /* массив структур */
 };
};

Если ifc_req равно NULL, то SIOCGIFCONF возвращает необходимый размер буфера в байтах для приёма всех доступных адресов в ifc_len. В противном случае ifc_req содержит указатель на массив структур ifreq, который будет заполнен адресами всех активных интерфейсов L3. В ifc_len содержится размер массива в байтах. Внутри каждой структуры ifreq в ifr_name будет записано имя интерфейса, а в ifr_addr — адрес. Реальное количество переданных байт возвращается в ifc_len.

Если размера, указанного в ifc_len, недостаточно для сохранения всех адресов, то ядро не запишет не поместившиеся и сообщит об успешном выполнении. Не существует надёжного способа обнаружения возникновения такой ситуации. Поэтому рекомендуется или сначала определить необходимый размер буфера вызовом SIOCGIFCONF с значением ifc_req равным NULL, или повторить вызов с большим буфером и проверить не отличается ли ifc_len на менее чем sizeof(struct ifreq) от первого значения.

Если произошла ошибка доступа к структуре ifconf или ifreq, то возвращается EFAULT.

Большинство протоколов поддерживают свои собственные вызовы ioctl для настройки предназначенных только для протокола параметров интерфейса. Подробности приведены в справочных страницах протоколов. Настройка адресов IP описывается в ip(7).

В дополнение ко всему, некоторые устройства поддерживают индивидуальные (private) вызовы ioctl. Здесь они не описаны.


4. Функция inet_ntoa

↑ К содержанию ↑

#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);

Функция преобразует 32-разрядный двоичный адрес IPv4, хранящийся в сетевом порядке байтов в struct in_addr (которая принадлежит struct sockaddr_in) в десятичную запись с точками (например, в запись вида "192.168.5.10").

Примечание. Вообще, эта функция немного устарела. Вместо нее можно применять  более новую функциу inet_ntop, которая способна работать не только с адресами IPv4 (как inet_ntoa), и с адресами IPv6. Для этого потребуется заменить вызовы вида

ptr = inet_ntoa(foo.sin_addr);

на

char str[INET_ADDRSTRLEN];
ptr = inet_ntop(AF_INET, &foo.sin_addr, str, sizeof(str));


5. Функция if_nameindex

↑ К содержанию ↑

#include <net/if.h>
struct if_nameindex *if_nameindex(void);
void if_freenameindex(struct if_nameindex *ptr);

Функция if_nameindex() возвращает массив структур if_nameindex, каждая из которых содержит информацию об одном из сетевых интерфейсов системы. Структура if_nameindex содержит по крайней мере следующие данные:

 unsigned int if_index; /* индекс интерфейса (1, 2, …) */
 char  *if_name;  /* имя с Null в конце («eth0», и т. д.) */

В поле if_index содержится индекс интерфейса. Значение поля ifa_name указывает на имя интерфейса (завершается null). Конец массива определяется по записи if_index с нулевым значением и по записи ifa_name со значением NULL.

Память под структуру данных, возвращаемая if_nameindex(), выделяется динамически и должна освобождаться с помощью if_freenameindex(), когда больше не нужна.


6. Интерфейсы

↑ К содержанию ↑

У Вас на компьютере могут быть, вообще говоря, интерфейсы с разными именами. Например, локальный интерфейс (которому соответствует IP-адрес 127.0.0.1) имеет имя lo.

Локальные (приватные) интерфейсы имеют, по умолчанию, имена eth0, eth1 и т.д. Например, интерфейс с именем eth0 может иметь IP_адрес 192.168.145.130.

Однако, имя интерфейса может быть изменено как программным путем, так и через команды консоли (bash). Кроме того, это возможно путем правки конфигурационных файлов.

В данном задании Вы научитесь считывать основные характеристики интерфейсов (IP-адрес и имя).



7. II. Практика

↑ К содержанию ↑

Ниже рассмотрим несколько программных листингов, выполненных на языке С (под Linux), которые позволяют узнать такие характеристики интерфейсов, как имя и IP-адрес.

Примечание. Скорее всего, для запуска программ потребуются права администратора (root).


8. Листинг 1

↑ К содержанию ↑

/*  ip1.c  */
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
struct in_addr addr;
int get_addr(const char *intf, struct in_addr *addr)
{
 int fd;
 if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return -1;
 struct ifreq ifr;
 strncpy(ifr.ifr_name, intf, IFNAMSIZ);
 if(ioctl(fd, SIOCGIFADDR, &ifr) < 0)
 {
 perror("ioctl SIOCGIFADDR");
 return -1;
 }
 *addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr;
 return 0;
}
int main()
{
 if (get_addr("eth0", &addr) < 0)
 {
 perror("get_addr");
 return -1;
 }
 printf("Приватный IP-адрес %s\n", inet_ntoa(addr));
 return 0;
}

Результатом должно быть что-то вроде:

Приватный IP-адрес 192.168.145.130


Как видим, листинг использует три системные структуры:

in_addr,
ifreq,
sockaddr_in.

Пользовательская функция get_addr обращается к сетевому интерфейсу с конкретным именем "eth0" и также ссылается на структуру addr (которая является наследницей структуры in_addr).

Пользовательская функция создает дэйтаграмный сокет и затем записывает в поле ifr_name структуры ifr заданное в программе имя сетевого интерфейса (т.е. eth0).

Затем, путем вызова функции ioctl, осуществляется обращение к сетевому интерфейсу, который создан сокетом, для целей получения IP-адреса этого интерфейса. Так как информация об этом содержится в структуре ifreq, вызов функции ioctl содержит ссылку на потомка этой структуры, т.е. на ifr. После чего из структуры ifreq в структуру ifr заносится информация об IP-адресе.

Затем происходит преобразование из массива байт в структуру sockaddr_in и считывание IP-адреса из нее путем обращения к полю sin_addr. Так как структура addr является глобальной для данного листинга, то из функции main можно получить к ней доступ.

Функция inet_ntoa применяется для преобразования сетевого порядка байт в обычный IP-адрес вида ХХХ.ХХХ.ХХХ.ХХХ (Network TO Ascii). Отметим, что аргумент функции inet_ntoa должен иметь тип sockaddr_in (или in_addr),
иначе она не выдаст результат вообще (программа завершится) или результат будет некорректным – «странным». Если это не так, то следует привести аргумент к этому типу, для чего перед ним следует указать

*(struct in_addr*)

Кстати, очистка полей структуры-потомка перед их заполнением – в этом листинге не используется. Что – несколько некорректно.  Неплохо бы исправить этот недостаток.


9. Листинг 2

↑ К содержанию ↑

/*  ip2.c  */
// Этот листинг выдает локальный (127.0.0.1) и локальный-приватный (например, 192.168.ххх.ххх) IP-адреса компьютера, на котором он будет запущен.
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <linux/socket.h>
//#include <linux/ioctl.h>
#include <linux/types.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <inttypes.h>
#include <netdb.h>

struct ifparam {
 __u32 ip;  // IP адрес
 __u32 mask;  // маска подсети
} ifp;
int getifconf(__u8 *intf, struct ifparam *ifp)
{
 int fd;
 struct sockaddr_in s;
 struct ifreq ifr; // см. <linux/if.h>
 memset((void *)&ifr, 0, sizeof(struct ifreq));
 if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)  return (-1);
 sprintf(ifr.ifr_name, "%s", intf);
/*
* Определяем IP адрес сетевого интерфейса
*/
 if(ioctl(fd, SIOCGIFADDR, &ifr) < 0) {
  perror("ioctl SIOCGIFADDR");
  return -1;
 }
 memset((void *)&s, 0, sizeof(struct sockaddr_in));
 memcpy((void *)&s, (void *)&ifr.ifr_addr, sizeof(struct sockaddr));
 memcpy((void *)&ifp->ip, (void *)&s.sin_addr.s_addr, sizeof(__u32));
 return 0;
}
int main()
{
/*
* Получаем параметры сетевого интерфейса eth0
*/
 if(getifconf("eth0", &ifp) < 0) {
  perror("getifconf");
  return -1;
 }
// Получаем локальный IP-адрес (127.0.0.1 или loopback)
  char *ac = "localhost";
  struct hostent *hosten= gethostbyname(ac);
int cnt;
u_long IpAddr;
for( cnt=0 ; hosten->h_addr_list[cnt] != NULL ; cnt++ ) {
  int Ip1,Ip2,Ip3,Ip4;
  IpAddr=ntohl(*((u_long *)(hosten -> h_addr_list[cnt])));
// 0xff - это в 16-ричном формате число 255
  Ip1=(int)( IpAddr>>24 ) & 0xff;
  Ip2=(int)( IpAddr>>16 ) & 0xff;
  Ip3=(int)( IpAddr>>8 ) & 0xff;
  Ip4=(int)( IpAddr&0xff );
  printf("Локальный IP-адрес (loopback) \%d = \%d.\%d.\%d.\%d\n",cnt,Ip1,Ip2,Ip3,Ip4);
 }
// Получаем приватный (он тоже называется локальным) IP-адрес
int IpAddr_my;
  // Исправьте следующую строчку в целях корректности
  for( cnt=0 ; hosten->h_addr_list[cnt] != NULL ; cnt++ ) {
  int Ip1,Ip2,Ip3,Ip4;
  IpAddr_my = ifp.ip;
  Ip1=(int)( IpAddr_my>>24 ) & 0xff;
  Ip2=(int)( IpAddr_my>>16 ) & 0xff;
  Ip3=(int)( IpAddr_my>>8 ) & 0xff;
  Ip4=(int)( IpAddr_my & 0xff);
// Почему IP-адрес выводится в обратном порядке, но результат получается правильный?
  printf("Приватный IP-адрес \%d = \%d.\%d.\%d.\%d\n",cnt,Ip4,Ip3,Ip2,Ip1);
 }
/*
* Отобразим полученные параметры сетевого интерфейса
*/
 printf("IpAddr: %lu\n", IpAddr);
 printf("IP адрес (__U32): %"PRIi32"\n",ifp.ip);
 return 0;
}


Эта программа аналогична предыдущей, но в ней используется еще одна структура:

struct ifparam;

Обратите внимание на функцию sprintf, которая в некотором смысле является аналогом функций memset, strncpy, но, является, вообще говоря, более высокоуровневой.

В функции main обратите внимание на строчку

char *ac = "localhost";

Если в кавычках задать иное доменное имя, например, имя какого-нибудь сайта (скажем, 4846d.ru), то программа выдаст уже не 127.0.0.1, а IP-адрес этого сайта.

Также обратите внимание на конструкции вида

Ip1=(int)( IpAddr>>24 ) & 0xff;

Самостоятельно изучите – КАК это работает. Имейте в виду, что IP-адрес:

  • в версии протокола IPv4 IP-адрес имеет длину 4 байта, (т.е. 32 бита), пространство адресов AF_INET)
  • в версии протокола IPv6 IP-адрес имеет длину 16 байт (пространство адресов AF_INET6).

Мы с Вами работаем с версией протокола IPv4.


Результат работы листинга 2 будет иметь примерно следующий вид:

Локальный IP-адрес 0 = 127.0.0.1
Приватный IP-адрес 0 = 192.168.145.130
IpAddr: 2130706433
IP адрес (__U32): -2104383296

Обратите внимание на последние две странные строчки. Что это за непонятные числа?


10. Листинг 3

↑ К содержанию ↑

Этот листинг взят с сайта: programmersclub.ru/­Взаимодействие-с-сетевыми-интерфейс/

#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <string.h>
#include <net/if.h>
#include <errno.h>
#include <stdio.h>

int main()
{
int sock;         // дескриптор сокета

struct sockaddr_in *in_addr;    // структура интернет адреса (поля)
struct ifreq ifr;       // структура - параметр
struct if_nameindex *ifNameIndex;   // структура интерфейсов и их индексов

sock = socket(AF_INET, SOCK_DGRAM, 0);   // открываем дескриптор сокета

if (sock < 0) {
printf("Не удалось открыть сокет, ошибка: %s\n", strerror(errno));
return 1;
}

ifNameIndex = if_nameindex();

if (ifNameIndex) {       // если удалось получить данные

while (ifNameIndex->if_index) {     // пока имеются данные
memset(&ifr, 0, sizeof(ifr)); // очищаем структуру  
strncpy(ifr.ifr_name, ifNameIndex->if_name, IFNAMSIZ);  // получаем имя следующего интерфейса

// получаем IP адрес с помощью SIOCGIFADDR, одновременно проверяя результат
if (ioctl(sock, SIOCGIFADDR, &ifr) < 0) {
printf("Не получить IP адрес для %s, ошибка: %s\n", ifr.ifr_name, strerror(errno));
close(sock);
return 1;
}

// преобразовываем из массива байт в структуру sockaddr_in
in_addr = (struct sockaddr_in *) &ifr.ifr_addr;

printf("Интерфейс %s индекс %i IP адрес: %s\n", ifr.ifr_name, ifNameIndex->if_index, inet_ntoa (in_addr-> sin_addr));
++ifNameIndex;       // переходим к следующему интерфейсу
}

}
close(sock);
return 0;
}

Этот листинг должен выдавать что-то вроде:
Интерфейс lo индекс 1 IP адрес: 127.0.0.1
Интерфейс eth0 индекс 2 IP адрес: 192.168.145.130

Примечание 1. Этот листинг может показать «интересный» результат в случае, когда не работает сеть по вине провайдера (который не пускает своих абонентов дальше своего IP-адреса). Если в такой ситуации зайти браузером на сайт провайдера, то, естественно, он может быть недоступным (не говоря уже о других сайтах). Однако, в списке ethX вполне может появиться ТРЕТИЙ интерфейс… IP_адрес которого будет являться IP-адресом провайдера… Это может означать, что последний «зачем-то» переключил Вас на локальное сетевое пространство без возможности выхода в сеть интернет, не давая ответа ВЫСОКОуровневым протоколам браузера – типа http, https – потому-то браузер и выдает ошибку при открытии страницы. Поэтому браузером может не открываться ни один сайт. А вот на низком уровне этот интерфейс вполне можно определить.

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

Примечание 2. Естественно, всеми данными листингами можно определить только(!) локальные адреса. Публичные IP-адреса возможно определить ТОЛЬКО путем обращения к сети интернет. Ибо назначает адрес не владелец компьютера, а сеть (например, провайдер). Например, в консоли можно набрать команду вида

wget -O - -q icanhazip.com

Если сеть доступна – в консоли появится Ваш (точнее, Вашего сетевого устройства) ПУБЛИЧНЫЙ IP-адрес.

Примечание 3. Нередко (при работе без виртуальной машины, в частности) сеть провайдера может являться локальной для подключенного к ней сетевого устройства (модема, телефона и т.п.).

Итак, вернемся к листингу 3.
Он перебирает ВСЕ сетевые интерфейсы, доступные функции if_nameindex();

А далее – все как обычно (см. Листинги  1 ,  2  ).


11. III. Самостоятельная разработка

↑ К содержанию ↑

Доработайте листинг 3 так, чтобы он:

  1. Работал резидентно (т.е. после запуска постоянно находился бы в памяти до тех пор, пока не будет нажат какой-нибудь символ на клавиатуре),
  2. Отслеживал резидентно (например, с интервалом в 1 секунду) все сетевые интерфейсы. И, если появляется новый интерфейс, или, наоборот, исчезает ранее функционировавший, программа должна выдавать в консоль соответствующее сообщение. Проверить это можно путем подключения / отключения сети интернет на компьютере (или в виртуальной машине, в которой установлен Linux Ubuntu)

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


С уважением, Салимоненко Д.А.