UNIX: разработка сетевых приложений — страница 19 из 42

Операции функции ioctl

17.1. Введение

Функция

ioctl
традиционно являлась системным интерфейсом, используемым для всего, что не входило в какую-либо другую четко определенную категорию. POSIX постепенно избавляется от функции
ioctl
, создавая заменяющие ее функции-обертки и стандартизуя их функциональность. Например, доступ к интерфейсу терминала Unix традиционно осуществлялся с помощью функции
ioctl
, но в POSIX были созданы 12 новых функций для терминалов:
tcgetattr
для получения атрибутов терминала,
tcflush
для опустошения буферов ввода или вывода, и т.д. Аналогичным образом POSIX заменяет одну сетевую функцию
ioctl
: новая функция
sockatmark
(см. раздел 24.3) заменяет команду
SIOCATMARK ioctl
. Тем не менее прочие сетевые команды
ioctl
остаются не стандартизованными и могут использоваться, например, для получения информации об интерфейсе и обращения к таблице маршрутизации и кэшу ARP (Address Resolution Protocol — протокол разрешения адресов).

В этой главе представлен обзор команд функции

ioctl
, имеющих отношение к сетевому программированию, многие из которых зависят от реализации. Кроме того, некоторые реализации, включая системы, происходящие от 4.4BSD и Solaris 2.6, используют сокеты домена
AF_ROUTE
(маршрутизирующие сокеты) для выполнения многих из этих операций. Маршрутизирующие сокеты мы рассматриваем в главе 18.

Обычно сетевые программы (как правило, серверы) используют функцию

ioctl
для получения информации обо всех интерфейсах узла при запуске программы, с тем чтобы узнать адрес интерфейса, выяснить, поддерживает ли интерфейс широковещательную передачу, многоадресную передачу и т.д. Для возвращения этой информации мы разработали нашу собственную функцию. В этой главе мы представляем ее реализацию с применением функции
ioctl
, а в главе 18 — другую реализацию, использующую маршрутизирующие сокеты.

17.2. Функция ioctl

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

fd
.

#include 


int ioctl(int fd, int request, ... /* void *arg */ );

Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки

Третий аргумент всегда является указателем, но тип указателя зависит от аргумента

request
.

ПРИМЕЧАНИЕ

В 4.4BSD второй аргумент имеет тип unsigned long вместо int, но это не вызывает проблем, поскольку в заголовочных файлах определены константы, используемые для данного аргумента. Пока прототип функции подключен к программе, система будет обеспечивать правильную типизацию.

Некоторые реализации определяют третий аргумент как неопределенный указатель (void*), а не так, как он определен в ANSI С.

Не существует единого стандарта заголовочного файла, определяющего прототип функции для ioctl, поскольку он не стандартизован в POSIX. Многие системы определяют этот прототип в файле , как это показываем мы, но традиционные системы BSD определяют его в заголовочном файле .

Мы можем разделить аргументы

request
, имеющие отношение к сети, на шесть категорий:

■ операции с сокетами;

■ операции с файлами;

■ операции с интерфейсами;

■ операции с кэшем ARP;

■ операции с таблицей маршрутизации;

■ операции с потоками (см. главу 31).

Помимо того, что, как показывает табл. 7.9, некоторые операции

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

В табл. 17.1 перечислены аргументы request вместе с типами данных, на которые должен указывать адрес

arg
. В последующих разделах эти вызовы рассматриваются более подробно.


Таблица 17.1. Обзор сетевых вызовов ioctl

КатегорияrequestОписаниеТип данных
СокетSIOCATMARKНаходится ли указатель чтения сокета на отметке внеполосных данныхint
SIOCSPGRPУстановка идентификатора процесса или идентификатора группы процессов для сокетаint
SIOCGPGRPПолучение идентификатора процесса или идентификатора группы процессов для сокетаint
ФайлFIONBIOУстановка/сброс флага отсутствия блокировкиint
FIOASYNCУстановка/сброс флага асинхронного ввода-выводаint
FIONREADПолучение количества байтов в приемном буфереint
FIOSETOWNУстановка идентификатора процесса или идентификатора группы процессов для файлаint
FIOGETOWNПолучение идентификатора процесса или идентификатора группы процессов для файлаint
ИнтерфейсSIOCGIFCONFПолучение списка всех интерфейсовstruct ifconf
SIOCSIFADDRУстановка адреса интерфейсаstruct ifreq
SIOCGIFADDRПолучение адреса интерфейсаstruct ifreq
SIOCSIFFLAGSУстановка флагов интерфейсаstruct ifreq
SIOCGIFFLAGSПолучение флагов интерфейсаstruct ifreq
SIOCSIFDSTADDRУстановка адреса типа «точка-точка»struct ifreq
SIOCGIFDSTADDRПолучение адреса типа «точка-точка»struct ifreq
SIOCGIFBRDADDRПолучение широковещательного адресаstruct ifreq
SIOCSIFBRDADDRУстановка широковещательного адресаstruct ifreq
SIOCGIFNETMASKПолучение маски подсетиstruct ifreq
SIOCSIFNETMASKУстановка маски подсетиstruct ifreq
SIOCGIFMETRICПолучение метрики интерфейсаstruct ifreq
SIOCSIFMETRICУстановка метрики интерфейсаstruct ifreq
SIOCxxx(Множество вариантов в зависимости от реализации)
ARPSIOCSARPСоздание/модификация элемента ARPstruct arpreq
SIOCGARPПолучение элемента ARPstruct arpreq
SIOCDARPУдаление элемента ARPstruct arpreq
МаршрутизацияSIOCADDRTДобавление маршрутаstruct rtentry
SIOCDELRTУдаление маршрутаstruct rtentry
ПотокиI_xxx(См. раздел 31.5)

17.3. Операции с сокетами

Существует три типа вызова, или запроса (в зависимости от значения аргумента

request
) функции
ioctl
, предназначенные специально для сокетов [128, с. 551–553]. Все они требуют, чтобы третий аргумент функции
ioctl
был указателем на целое число.

SIOCATMARK
. Возвращает указатель на ненулевое значение в качестве третьего аргумента (его тип, как только что было сказано, — указатель на целое число), если указатель чтения сокета в настоящий момент находится на отметке внеполосных данных (out-of-band mark), или указатель на нулевое значение, если указатель чтения сокета не находится на этой отметке. Более подробно внеполосные данные (out-of-band data) рассматриваются в главе 24. POSIX заменяет этот вызов функцией
sockatmark
, и мы рассматриваем реализацию этой новой функции с использованием функции
ioctl
в разделе 24.3.

SIOCGRP
. Возвращает в качестве третьего аргумента указатель на целое число — идентификатор процесса или группы процессов, которым будут посылаться сигналы
SIGIO
или
SIGURG
по окончании выполнения асинхронной операции или при появлении срочных данных. Этот вызов идентичен вызову
F_GETOWN
функции
fcntl
, и в табл. 7.9 мы отмечали, что POSIX стандартизирует функцию
fcntl
.

SIOCSPGRP
. Задает идентификатор процесса или группы процессов для отсылки им сигналов
SIGIO
или
SIGURG
как целое число, на которое указывает третий аргумент. Этот вызов идентичен вызову
F_SETOWN
функции
fcntl
, и в табл. 7.9 мы отмечали, что POSIX стандартизирует функцию
fcntl
.

17.4. Операции с файлами

Следующая группа вызовов начинается с

FIO
и может применяться к определенным типам файлов в дополнение к сокетам. Мы рассматриваем только вызовы, применимые к сокетам [128, с. 553].

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

ioctl
указывал на целое число.

FIONBIO
. Флаг отключения блокировки при выполнении операций ввода-вывода сбрасывается или устанавливается в зависимости от третьего аргумента функции
ioctl
. Если этот аргумент является пустым указателем, то флаг сбрасывается (блокировка разрешена). Если же третий аргумент является указателем на единицу, то включается неблокируемый ввод-вывод. Этот вызов обладает тем же действием, что и команда
F_SETFL
функции
fcntl
, которая позволяет установить или сбросить флаг
O_NONBLOCK
, задающий статус файла.

FIOASYNC
. Флаг, управляющий получением сигналов асинхронного ввода-вывода (
SIGIO
), устанавливается или сбрасывается для сокета в зависимости от того, является ли третий аргумент функции
ioctl
пустым указателем. Этот флаг имеет то же действие, что и флаг статуса файла
O_ASYNC
, который можно установить и сбросить с помощью команды
F_SETFL
функции
ioctl
.

FIONREAD
. Возвращает число байтов, в настоящий момент находящихся в приемном буфере сокета, как целое число, на которое указывает третий аргумент функции
ioctl
. Это свойство работает также для файлов, каналов и терминалов. Более подробно об этом вызове мы рассказывали в разделе 14.7.

FIOSETOWN
. Эквивалент
SIOCSPGRP
для сокета.

FIOGETOWN
. Эквивалент
SIOCGPGRP
для сокета.

17.5. Конфигурация интерфейса

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

SIOCGIFCONF
, использующего структуру
ifconf
, которая, в свою очередь, использует структуру
ifreq
. Обе эти структуры показаны в листинге 17.1[1].

Листинг 17.1. Структуры ifconf и ifreq, используемые в различных вызовах функции ioctl, относящихся к интерфейсам

// struct ifconf {

 int ifc_len; /* размер буфера, "значение-результат" */

 union {

  caddr_t      ifcu_buf;  /* ввод от пользователя к ядру */

  struct ifreq *ifcu_req; /* ядро возвращает пользователю */

 } ifc_ifcu;

};

#define ifc_buf ifc_ifcu.ifcu_buf /* адрес буфера */

#define ifc_req ifc_ifcu.ifcu_req /* массив возвращенных структур */


#define IFNAMSIZ 16


struct ifreq {

 char ifr_name[IFNAMSIZ]; /* имя интерфейса, например "le0" */

 union {

  struct sockaddr ifru_addr;

  struct sockaddr ifru_dstaddr;

  struct sockaddr ifru_broadaddr;

  short           ifru_flags;

  int             ifru_metric;

  caddr_t         ifru_data;

 } ifr_ifru;

};

#define ifr_addr ifr_ifru.ifru_addr       /* адрес */

#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* другой конец линии передачи, называемой

                                             "точка-точка" */

#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* широковещательный адрес */

#define ifr_flags ifr_ifru.ifru_flags     /* флаги */

#define ifr_metric ifr_ifru.ifru_metric   /* метрика */

#define ifr_data ifr_ifru.ifru_data       /* с использованием интерфейсом */

Прежде чем вызвать функцию

ioctl
, мы выделяем в памяти место для буфера и для структуры
ifconf
, а затем инициализируем эту структуру. Мы показываем это на рис. 17.1, предполагая, что наш буфер имеет размер 1024 байта. Третий аргумент функции
ioctl
— это указатель на нашу структуру
ifconf
.

Рис. 17.1. Инициализация структуры ifconf перед вызовом SIOCGIFCONF

Если мы предположим, что ядро возвращает две структуры

ifreq
, то при завершении функции
ioctl
мы можем получить ситуацию, представленную на рис. 17.2. Затененные области были изменены функцией
ioctl
. Буфер заполняется двумя структурами, и элемент
ifc_len
структуры
ifconf
обновляется, с тем чтобы соответствовать количеству информации, хранимой в буфере. Предполагается, что на этом рисунке каждая структура
ifreq
занимает 32 байта.

Рис. 17.2. Значения, возвращаемые в результате вызова SIOCGIFCONF

Указатель на структуру

ifreq
также используется в качестве аргумента оставшихся функций
ioctl
интерфейса, показанных в табл. 17.1, которые мы описываем в разделе 17.7. Отметим, что каждая структура
ifreq
содержит объединение (
union
), а директивы компилятора
#define
позволяют непосредственно обращаться к полям объединения по их именам. Помните о том, что в некоторых системах в объединение
ifr_ifru
добавлено много зависящих от реализации элементов.

17.6. Функция get_ifi_info

Поскольку многим программам нужно знать обо всех интерфейсах системы, мы разработаем нашу собственную функцию

get_ifi_info
, возвращающую связный список структур — по одной для каждого активного в настоящий момент интерфейса. В этом разделе мы покажем, как эта функция реализуется с помощью вызова
SIOCGIFCONF
функции
ioctl
, а в главе 18 мы создадим ее другую версию, использующую маршрутизирующие сокеты.

ПРИМЕЧАНИЕ

BSD/OS предоставляет функцию getifaddrs, имеющую аналогичную функциональность.

Поиск по всему дереву исходного кода BSD/OS 2.1 показывает, что 12 программ выполняют вызов SIOCGIFCONF функции ioctl для определения присутствующих интерфейсов.

Сначала мы определяем структуру

ifi_info
в новом заголовочном файле, который называется
unpifi.h
, показанном в листинге 17.2.

Листинг 17.2. Заголовочный файл unpifi.h

//ioctl/unpifi.h

 1 /* Наш собственный заголовочный файл для программ, которым требуется

 2 информация о конфигурации интерфейса. Включаем его вместо "unp.h". */


 3 #ifndef __unp_ifi_h

 4 #define __unp_ifi_h


 5 #include "unp.h"

 6 #include 


 7 #define IFI_NAME 16 /* то же, что и IFNAMSIZ в заголовке  */

 8 #define IFI_HADDR 8 /* с учетом 64-битового интерфейса EUI-64 в будущем */


 9 struct ifi_info {

10  char ifi_name[IFI_NAME];     /* имя интерфейса, заканчивается

                                    символом конца строки */

11  short ifi_index;             /* индекс интерфейса */

12  short ifi_mtu;               /* MTU для интерфейса */

13  u_char ifi_haddr[IFI_HADDR]; /* аппаратный адрес */

14  u_short ifi_hlen; /* количество байтов в аппаратном адресе: 0, 6, 8 */

15  short ifi_flags;  /* константы IFF_xxx из  */

16  short if_myflags; /* наши флаги IFI_xxx */

17  struct sockaddr *ifi_addr;    /* первичный адрес */

18  struct sockaddr *ifi_brdaddr; /* широковещательный адрес */

19  struct sockaddr *ifi_dstaddr; /* адрес получателя */

20 s truct ifi_info *ifi_next;    /* следующая из этих структур */

21 };


22 #define IFI_ALIAS 1 /* ifi_addr - это псевдоним */


23 /* прототипы функций */

24 struct ifi_info *get_ifi_info((int, int);

25 struct ifi_info *Get_ifi_info(int, int);

26 void free_ifi_info(struct ifi_info*);


27 #endif /* _unp_ifi_h */

9-21
 Связный список этих структур возвращается нашей функцией. Элемент
ifi_next
каждой структуры указывает на следующую структуру. Мы возвращаем в этой структуре информацию, которая может быть востребована в типичном приложении: имя интерфейса, индекс интерфейса, MTU, аппаратный адрес (например, адрес Ethernet), флаги интерфейса (чтобы позволить приложению определить, поддерживает ли приложение широковещательную или многоадресную передачу и относится ли этот интерфейс к типу «точка-точка»), адрес интерфейса, широковещательный адрес, адрес получателя для связи «точка-точка». Вся память, используемая для хранения структур
ifi_info
вместе со структурами адреса сокета, содержащимися в них, выделяется динамически. Следовательно, мы также предоставляем функцию
free_ifi_info
для освобождения всей этой памяти.

Перед тем как представить реализацию нашей функции

ifi_info
, мы покажем простую программу, которая вызывает эту функцию и затем выводит информацию. Эта программа, представленная в листинге 17.3, является уменьшенной версией программы
ifconfig
.

Листинг 17.3. Программа prifinfo, вызывающая нашу функцию ifi_info

//ioctl/prifinfo.c

 1 #include "unpifi.h"


 2 int

 3 main(int argc, char **argv)

 4 {

 5  struct ifi_info *ifi, *ifihead;

 6  struct sockaddr *sa;

 7  u_char *ptr;

 8  int i, family, doaliases;


 9  if (argc != 3)

10   err_quit("usage: prifinfo ");


11  if (strcmp(argv[1], "inet4") == 0)

12   family = AF_INET;

13  else if (strcmp(argv[1], "inet6") == 0)

14   family = AF_INET6;

15  else

16   err_quit("invalid ");

17  doaliases = atoi(argv[2]);


18  for (ifihead = ifi = Get_ifi_info(family, doaliases);

19  ifi ! = NULL; ifi = ifi->ifi_next) {

20   printf("%s: <", ifi->ifi_name);

21   if (ifi->ifi_index != 0)

22    printf("%d) ", ifi->ifi_index);

23   printf("<");

24   if (ifi->ifi_flags & IFF_UP) printf ("UP ");

25   if (ifi->ifi_flags & IFF_BROADCAST) printf("BCAST ");

26   if (ifi->ifi_flags & IFF_MULTICAST) printf("MCAST ");

27   if (ifi->ifi_flags & IFF_LOOPBACK) printf("LOOP ");

28   if (ifi->ifi_flags & IFF_POINTOPOINT) printf("P2P ");

29   printf(">\n");

30   if ((i = ifi->ifi_hlen) > 0) {

31    ptr = ifi->ifi_haddr;

32    do {

33     printf("%s%x", (i == ifi->ifi_hlen) ? " " : ":", *ptr++);

34    } while (--i > 0);

35    printf("\n");

36   }

37   if (ifi->ifi_mtu != 0)

38    printf(" MTU: %d\n". ifi->ifi_mtu);

39   if ((sa = ifi->ifi_addr) != NULL)

40    printf(" IP addr: %s\n", Sock_ntop_host(sa, sizeof(*sa)));

41   if ((sa = ifi->ifi_brdaddr) != NULL)

42    printf(" broadcast addr, %s\n",

43     Sock_ntop_host(sa, sizeof(*sa)));

44   if ((sa = ifi->ifi_dstaddr) != NULL)

45    printf(" destination addr %s\n\",

46     Sock_ntop_host(sa, sizeof(*sa)));

47   }

48  free_ifi_info(ifihead);

49  exit(0);

59 }

18-47
 Программа представляет собой цикл
for
, в котором один раз вызывается функция
get_ifi_info
, а затем последовательно перебираются все возвращаемые структуры
ifi_info
.

20-36
 Выводятся все имена интерфейсов и флаги. Если длина аппаратного адреса больше нуля, он выводится в виде шестнадцатеричного числа (наша функция
get_ifi_info
возвращает нулевую длину
ifi_hlen
, если адрес недоступен).

37-46
 Выводится MTU и те IP-адреса, которые были возвращены.

Если мы запустим эту программу на нашем узле

macosx
(см. рис. 1.7), то получим следующий результат:

macosx % prifinfo inet4 0

lo0: 

 MTU: 16384

 IP addr: 127.0.0.1

en1: 

 MTU: 1500

 IP addr: 172.24.37.78

 broadcast addr: 172.24.37.95

Первый аргумент командной строки

inet4
задает адрес IPv4, а второй, нулевой аргумент указывает, что не должно возвращаться никаких псевдонимов, или альтернативных имен (альтернативные имена IP-адресов мы описываем в разделе А.4). Обратите внимание, что в MacOS X аппаратный адрес интерфейса Ethernet недоступен.

Если мы добавим к интерфейсу Ethernet (

en1
) три альтернативных имени адреса с идентификаторами узла 79, 80 и 81 и изменим второй аргумент командной строки на 1, то получим:

macosx % prifinfo inet4 1

lo0: 

 MTU: 16384

 IP addr: 127.0.0.1

en1: 

 MTU: 1500

 IP addr: 172.24.37.78 первичный IP-адрес

 broadcast addr: 172.24.37.95

en1: 

 MTU: 1500

 IP addr: 172.24.37.79 первый псевдоним

 broadcast addr: 172.24.37.95

en1: 

 MTU: 1500

 IP addr: 172 24.37.80 второй псевдоним

 broadcast addr: 172.24 37.95

en1: 

 MTU: 1500

 IP addr: 172 24.37.81 третий псевдоним

 broadcast addr: 172.24.37 95

Если мы запустим ту же программу под FreeBSD, используя реализацию функции

get_ifi_info
, приведенную в листинге 18.9 (которая может легко получить аппаратный адрес), то получим:

freebsd4 % prifinfo inet4 1

de0: 

 0:80:c8:2b:d9:28

 IP addr: 135.197.17.100

 broadcast addr: 135.197.17.255

de1: 

 0:40:5:42:d6:de

 IP addr: 172.24.37.94 основной IP-адрес

 broadcast addr: 172.24.37.95

ef0: 

 0:40:5:42:d6:de

 IP addr: 172.24.37.93 псевдоним

 broadcast addr: 172.24.37.93

lo0: 

 IP addr: 127.0.0.1

В этом примере мы указали программе выводить псевдонимы, и мы видим, что один из псевдонимов определен для второго интерфейса Ethernet (

de1
) с идентификатором узла 93.

Теперь мы покажем нашу реализацию функции

get_ifi_info
, использующую вызов
SIOCGIFCONF
функции
ioctl
. В листинге 17.4 показана первая часть этой функции, получающая от ядра конфигурацию интерфейса.

Листинг 17.4. Выполнение вызова SIOCGIFCONF для получения конфигурации интерфейса

//lib/get_if_info.c

 1 #include "unpifi.h"


 2 struct ifi_info*

 3 get_ifi_info(int family, int doaliases)

 4 {

 5  struct ifi_info *ifi, *ifihead, **ifipnext;

 6  int sockfd, len, lastlen, flags, myflags, idx = 0, hlen = 0;

 7  char *ptr, *buf, lastname[IFNAMSIZ], *cptr, *haddr, *sdlname;

 8  struct ifconf ifc;

 9  struct ifreq *ifr, ifrcopy;

10  struct sockaddr_in *sinptr;

11  struct sockaddr_in6 *sin6ptr;


12  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);


13  lastlen = 0;

14  len = 100 * sizeof(struct ifreq); /* начальное приближение к нужному размеру буфера */

15  for (;;) {

16   buf = Mallос(len);

17   ifc.ifc_len = len;

18   ifc.ifc_buf = buf;

19   if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0) {

20    if (errno != EINVAL || lastlen != 0)

21     err_sys("ioctl error");

22   } else {

23    if (ifc.ifc_len == lastlen)

24    break; /* успех, значение len не изменилось */

25    lastlen = ifc.ifc_len;

26   }

27   len += 10 * sizeof(struct ifreq); /* приращение */

28   free(buf);

29  }

30  ifihead = NULL;

31  ifipnext = &ifihead;

32  lastname[0] = 0;

33  sdlname = NULL;

Создание сокета Интернета

11
 Мы создаем сокет UDP, который будет использоваться с функциями
ioctl
. Может применяться как сокет TCP, так и сокет UDP [128, с. 163].

Выполнение вызова SIOCGIFCONF в цикле

12-28
 Фундаментальной проблемой, связанной с вызовом
SIOCGIFCONF
, является то, что некоторые реализации не возвращают ошибку, если буфер слишком мал для хранения полученного результата [128, с. 118–119]. В этом случае результат просто обрезается так, чтобы поместиться в буфер, и функция
ioctl
возвращает нулевое значение, что соответствует успешному выполнению. Это означает, что единственный способ узнать, достаточно ли велик наш буфер, — сделать вызов, сохранить возвращенную длину, снова сделать вызов с большим размером буфера и сравнить полученную длину со значением, сохраненным из предыдущего вызова. Только если эти две длины одинаковы, наш буфер можно считать достаточно большим.

ПРИМЕЧАНИЕ

Беркли-реализации не возвращают ошибку, если буфер слишком мал [128, с. 118-199], и результат просто обрезается так, чтобы поместиться в существующий буфер. Solaris 2.5 возвращает ошибку EINVAL, если возвращаемая длина больше или равна длине буфера. Но мы не можем считать вызов успешным, если возвращаемая длина меньше размера буфера, поскольку Беркли-реализации могут возвращать значение, меньшее размера буфера, если часть структуры в него не помещается.

В некоторых реализациях предоставляется вызов SIOCGIFNUM, который возвращает число интерфейсов. Это позволяет приложению перед выполнением вызова SIOCGIFCONF выделить в памяти место для буфера достаточного размера, но такой подход не является широко распространенным.

Выделение в памяти места под буфер фиксированного размера для результата вызова SIOCGIFCONF стало проблемой с ростом Сети, поскольку большие веб-серверы используют много альтернативных адресов для одного интерфейса. Например, в Solaris 2.5 был предел в 256 альтернативных адресов для интерфейса, но в версии 2.6 этот предел вырос до 8192. Обнаружилось, что на сайтах с большим числом альтернативных адресов перестают работать программы с буферами фиксированного размера для размещения информации об интерфейсе. Хотя Solaris возвращает ошибку, если буфер слишком мал, эти программы размещают в памяти буфер фиксированного размера, запускают функцию ioctl, но затем перестают работать при возвращении ошибки.

12-15
 Мы динамически размещаем в памяти буфер начиная с размера, достаточного для 100 структур
ifreq
. Мы также отслеживаем длину, возвращаемую последним вызовом
SIOCGIFCONF
в
lastlen
, и инициализируем ее нулем.

19-20
 Если функция
ioctl
возвращает ошибку
EINVAL
и функция еще не возвращалась успешно (то есть
lastlen
все еще равно нулю), значит, мы еще не выделили буфер достаточного размера, поэтому мы продолжаем выполнять цикл.

22-23
 Если функция
ioctl
завершается успешно и возвращаемая длина равна
lastlen
, значит, длина не изменилась (наш буфер имеет достаточный размер), и мы с помощью функции
break
выходим из цикла, так как у нас имеется вся информация.

26-27
 В каждом проходе цикла мы увеличиваем размер буфера для хранения еще 10 структур
ifreq
.

Инициализация указателей связного списка

29-31
 Поскольку мы будем возвращать указатель на начало связного списка структур
ifi_info
, мы используем две переменные
ifihead
и
ifipnext
для хранения указателей на список по мере его создания.

Следующая часть нашей функции

get_ifi_info
, содержащая начало основного цикла, показана в листинге 17.5.

Листинг 17.5. Конфигурация интерфейса процесса

//lib/get_ifi_info.c

34 for (ptr = buf; ptr < buf + ifc.ifc_len; ) {

35  ifr = (struct ifreq*)ptr;


36 #ifdef HAVE_SOCKADDR_SA_LEN

37  len = max(sizeof(struct sockaddr), ifr->ifr_addr.sa_len);

38 #else

39  switch (ifr->ifr_addr.sa_family) {

40 #ifdef IPV6

41  case AF_INET6:

42   len = sizeof(struct sockaddr_in6);

43   break;

44 #endif

45  case AF_INET:

46  default:

47   len = sizeof(struct sockaddr);

48   break;

49  }

50 #endif /* HAVE_SOCKADDR_SA_LEN */

51  ptr += sizeof(ifr->ifr_name) + len; /* для следующей строки */


52 #ifdef HAVE_SOCKADDR_DL_STRUCT

53  /* предполагается, что AF_LINK идет перед AF_INET и AF_INET6 */

54  if (ifr->ifr_addr.sa_family == AF_LINK) {

55   struct sockaddr_dl *sdl = (struct sockaddr_dl*)&ifr->ifr_addr;

56   sdlname = ifr->ifr_name;

57   idx = sdl->sdl_index;

58   haddr = sdl->sdl_data + sdl->sdl_nlen;

59   hlen = sdl->sdl_alen;

60  }

61 #endif


62  if (ifr->ifr_addr.sa_family != family)

63   continue; /* игнорируется, если семейство адреса не то */

64  myflags = 0;

65  if ((cptr = strchr(ifr->ifr_name, ':')) != NULL)

66   *cptr = 0; /* замена двоеточия нулем */

67  if (strncmp(lastname, ifr->ifr_name, IFNAMSIZ) == 0) {

68   if (doaliases == 0)

69    continue; /* этот интерфейс уже обработан */

70   myflags = IFI_ALIAS;

71  }

72  memcpy(lastname, ifr->ifr_name, IFNAMSIZ);


73  ifrcopy = *ifr;

74  Ioctl(sockfd, SIOCGIFFLAGS, &ifrcopy);

75  flags = ifrcopy.ifr_flags;

76  if ((flags & IFF_UP) == 0)

77   continue; /* игнорируется, если интерфейс не используется */

Переход к следующей структуре адреса сокета

35-51
 При последовательном просмотре всех структур i
freq ifr
указывает на текущую структуру, а мы увеличиваем
ptr
на единицу, чтобы он указывал на следующую. Необходимо предусмотреть особенность более новых систем, предоставляющих поле длины для структур адреса сокета, и вместе с тем учесть, что более старые системы этого поля не предоставляют. Хотя в листинге 17.1 структура адреса сокета, содержащаяся в структуре
ifreq
, объявляется как общая структура адреса сокета, в новых системах она может относиться к произвольному типу. Действительно, в 4.4BSD структура адреса сокета канального уровня также возвращается для каждого интерфейса [128, с. 118]. Следовательно, если поддерживается элемент длины, то мы должны использовать его значение для переустановки нашего указателя на следующую структуру адреса сокета. В противном случае мы определяем длину, исходя из семейства адресов, используя размер общей структуры адреса сокета (16 байт) в качестве значения по умолчанию.

ПРИМЕЧАНИЕ

В системах, поддерживающих IPv6, не оговаривается, возвращается ли адрес IPv6 вызовом SIOCGIFCONF. Для более новых систем мы вводим оператор case, в котором предусмотрена возможность возвращения адресов IPv6. Проблема состоит в том, что объединение в структуре ifreq определяет возвращаемые адреса как общие 16-байтовые структуры sockaddr, подходящие для 16-байтовых структур sockaddr_in IPv4, но для 24-байтовых структур sockaddr_in6 IPv6 они слишком малы. В случае возвращения адресов IPv6 возможно некорректное поведение существующего кода, созданного в предположении, что в каждой структуре ifreq содержится структура sockaddr фиксированного размера. В системах, где структура sockaddr имеет поле sa_len, никаких проблем не возникает, потому что такие системы легко могут указывать размер структур sockaddr.

52-60
 Если система возвращает структуры
sockaddr
семейства
AF_LINK
в
SIOCGIFCONF
, мы копируем индекс интерфейса и данные об аппаратном адресе из таких структур.

62-63
 Мы игнорируем все адреса из семейств, отличных от указанного вызывающим процессом в аргументе функции
get_ini_info
.

Обработка альтернативных имен

64-72
 Нам нужно обнаружить все альтернативные имена (псевдонимы), которые могут существовать для интерфейса, то есть присвоенные этому интерфейсу дополнительные адреса. Обратите внимание в наших примерах, следующих за листингом 17.3, что в Solaris псевдоним содержит двоеточие, в то время как в 4.4BSD имя интерфейса в псевдониме не изменяется. Чтобы обработать оба случая, мы сохраняем последнее имя интерфейса в
lastname
и сравниваем его только до двоеточия, если оно присутствует. Если двоеточия нет, мы игнорируем этот интерфейс в том случае, когда имя эквивалентно последнему обработанному интерфейсу.

Получение флагов интерфейса

73-77
 Мы выполняем вызов
SIOCGIFFLAGS
функции
ioctl
(см. раздел 16.5), чтобы получить флаги интерфейса. Третий аргумент функции
ioctl
— это указатель на структуру
ifreq
, содержащую имя интерфейса, для которого мы хотим получить флаги. Мы создаем копию структуры
ifreq
, перед тем как запустить функцию ioctl, поскольку в противном случае этот вызов перезаписал бы IP-адрес интерфейса, потому что оба они являются элементами одного и того же объединения из листинга 17.1. Если интерфейс не активен, мы игнорируем его.

В листинге 17.6 представлена третья часть нашей функции.

Листинг 17.6. Получение и возвращение адресов интерфейса

//ioctl/get_ifi_infо.c

78   ifi = Calloc(1, sizeof(struct ifi_info));

79   *ifipnext = ifi; /* prev указывает на новую структуру */

80   ifipnext = &ifi->ifi_next; /* сюда указывает указатель на

                                   следующую структуру */


81   ifi->ifi_flags = flags; /* значения IFF_xxx */

82   ifi->ifi_myflags = myflags; /* значения IFI_xxx */

83 #if defined(SIOCGIFMTU) && defined(HAVE_STRUCT_IFREQ_IFR_MTU)

84   Ioctl(sockfd, SIOCGIFMTU, &ifrcopy);

85   ifi->ifi_mtu = ifrcopy.ifr_mtu;

86 #else

87   ifi->ifi_mtu = 0;

88 #endif

89   memcpy(ifi->ifi_name, ifr->ifr_name, IFI_NAME);

90   ifi->ifi_name[IFI_NAME-1] = '\0';

91   /* если sockaddr_dl относится к другому интерфейсу, он игнорируется */

92   if (sdlname == NULL || strcmp(sdlname, ifr->ifr_name) != 0)

93    idx = hlen = 0;

94   ifi->ifi_index = idx;

95   ifi->ifi_hlen = hlen;

96   if (ifi->ifi_hlen > IFI_HADDR)

97    ifi->ifi_hlen = IFI_HADDR;

98   if (hlen)

99    memcpy(ifi->ifi_haddr, haddr, ifi->ifi_hlen);

Выделение памяти и инициализация структуры ifi_info

78-99
На этом этапе мы знаем, что возвратим данный интерфейс вызывающему процессу. Мы выделяем память для нашей структуры
ifi_info
и добавляем ее в конец связного списка, который мы создаем. Мы копируем флаги и имя интерфейса в эту структуру. Далее мы проверяем, заканчивается ли имя интерфейса нулем, и поскольку функция
callос
инициализирует выделенную в памяти область нулями, мы знаем, что
ifi_hlen
инициализируется нулем, a
ifi_next
— пустым указателем.

В листинге 17.7 представлена последняя часть нашей функции.

Листинг 17.7. Получение и возврат адреса интерфейса

100   switch (ifr->ifr_addr.sa_family) {

101   case AF_INET:

102    sinptr = (struct sockaddr_in*)&ifr->ifr_addr;

103    ifi->ifi_addr = Calloc(1, sizeof(struct sockaddr_in));

104    memcpy(ifi->ifi_addr, sinptr, sizeof(struct sockaddr_in));


105 #ifdef SIOCGIFBRDADDR

106    if (flags & IFF_BROADCAST) {

107     Ioctl(sockfd, SIOCGIFBRDADDR, &ifrcopy);

108     sinptr = (struct sockaddr_in*) &ifrcopy.ifr_broadaddr;

109     ifi->ifi_brdaddr = Calloc(1, sizeof(struct sockaddr_in));

110     memcpy(ifi->ifi_brdaddr, sinptr, sizeof(struct sockaddr_in));

111    }

112 #endif


113 #ifdef SIOCGIFDSTADDR

114    if (flags & IFF_POINTOPOINT) {

115     Ioctl(sockfd, SIOCGIFDSTADDR, &ifrcopy);

116     sinptr = (struct sockaddr_in*) &ifrcopy.ifr_dstaddr;

117     ifi->ifi_dstaddr = Calloc(1, sizeof(struct sockaddr_in));

118     memcpy(ifi->ifi_dstaddr, sinptr, sizeof(struct sockaddr_in));

119    }

120 #endif

121    break;


122   case AF_INET6:

123    sin6ptr = (struct sockaddr_in6*)&ifr->ifr_addr;

124    ifi->ifi_addr = Calloc(1, sizeof(struct sockaddr_in6));

125    memcpy(ifi->ifi_addr, sin6ptr, sizeof(struct sockaddr_in6));


126 #ifdef SIOCGIFDSTADDR

127    if (flags & IFF_POINTOPOINT) {

128     Ioctl(sockfd, SIOCGIFDSTADDR, &ifrcopy);

129     sin6ptr = (struct sockaddr_in6*)&ifrcopy.ifr_dstaddf;

130     ifi->ifi_dstaddr = Calloc(1, sizeof(struct sockaddr_in6));

131     memcpy(ifi->ifi_dstaddr, sin6ptr,

132     sizeof(struct sockaddr_in6));

133    }

134 #endif

135    break;


136   default:

137    break;

138   }

139  }

140  free(buf);

141  return(ifihead); /* указатель на первую структуру в связной списке */

142 }

102-104
 Мы копируем IP-адрес, возвращенный из нашего начального вызова
SIOCGIFCONF
функции
ioctl
, в структуру, которую мы создаем.

106-119
 Если интерфейс поддерживает широковещательную передачу, мы получаем широковещательный адрес с помощью вызова
SIOCGIFBRDADDR
функции
ioctl
. Мы выделяем память для структуры адреса сокета, содержащей этот адрес, и добавляем ее к структуре
ifi_info
, которую мы создаем. Аналогично, если интерфейс является интерфейсом типа «точка-точка», вызов
SIOCGIFBRDADDR
возвращает IP-адрес другого конца связи.

123-133
 Обработка случая IPv6 — полная аналогия IPv4 за тем исключением, что вызов
SIOCGIFBRDADDR
не делается, потому что IPv6 не поддерживает широковещательную передачу.

В листинге 17.8 показана функция

free_ifi_info
, которой передается указатель, возвращенный функцией
get_ifi_info
. Эта функция освобождает всю динамически выделенную память.

Листинг 17.8. Функция free_ifi_info: освобождение памяти, которая была динамически выделена функцией get_ifi_info

//iосtl/get_ifi_info.c

143 void

144 free_ifi_info(struct ifi_info *ifihead)

145 {

146  struct ifi_info *ifi, *ifinext;


147  for (ifi = ifihead; ifi != NULL; ifi = ifinext) {

148   if (ifi->ifi_addr != NULL)

149    free(ifi->ifi_addr);

150   if (ifi->ifi_brdaddr != NULL)

151    free(ifi->ifi_brdaddr);

152   if (ifi->ifi_dstaddr != NULL)

153    free(ifi->ifi_dstaddr);

154   ifinext = ifi->ifi_next; /* невозможно получить ifi_next

                                   после вызова freed */

155   free(ifi);

156  }

157 }

17.7. Операции с интерфейсами

Как мы показали в предыдущем разделе, запрос

SIOCGIFCONF
возвращает имя и структуру адреса сокета для каждого сконфигурированного интерфейса. Существует множество других вызовов, позволяющих установить или получить все остальные характеристики интерфейса. Версия
get
этих вызовов (
SIOCGxxx
) часто запускается программой
netstat
, а версия
set
(
SIOCSxxx
) — программой
ifconfig
. Любой пользователь может получить информацию об интерфейсе, в то время как установка этой информации требует прав привилегированного пользователя.

Эти вызовы получают или возвращают структуру

ifreq
, адрес которой задается в качестве третьего аргумента функции
ioctl
. Интерфейс всегда идентифицируется по имени:
le0
,
lo0
,
ppp0
, — то есть по имени, заданному в элементе
ifr_name
структуры
ifreq
.

Многие из этих запросов используют структуру адреса сокета, для того чтобы задать или возвратить IP-адрес или маску адреса. Для IPv4 адрес или маска содержится в элементе

sin_addr
из структуры адреса сокета Интернета. Для IPv6 они помещаются в элемент
sin6_addr
структуры адреса сокета IPv6.

SIOCGIFADDR
. Возвращает адрес направленной передачи в элементе
ifr_addr
.

SIOCSIFADDR
. Устанавливает адрес интерфейса из элемента
ifr_addr
. Также вызывается функция инициализации для интерфейса.

SIOCGIFFLAGS
. Возвращает флаги интерфейса в элементе
ifr_flags
. Имена различных флагов определяются в виде
IFF_xxx
в заголовочном файле
. Флаги указывают, например, включен ли интерфейс (
IFF_UP
), является ли он интерфейсом типа «точка-точка» (
IFF_POINTOPOINT
), поддерживает ли широковещательную передачу (
IFF_BROADCAST
) и т.д.

SIOCSIFFLAGS
. Устанавливает флаги из элемента
ifr_flags
.

SIOCGIFDSTADDR
. Возвращает адрес типа «точка-точка» в элементе
ifr_dstaddr
.

SIOCSIFDSTADDR
. Устанавливает адрес типа «точка-точка» из элемента
ifr_dstaddr
.

SIOCGIFBRDADDR
. Возвращает широковещательный адрес в элементе
ifr_broadaddr
. Приложение сначала должно получить флаги интерфейса, а затем сделать корректный вызов:
SIOCGIFBRDADDR
для широковещательного интерфейса или
SIOCGIFDSTADDR
— для интерфейса типа «точка-точка».

SIOCSIFBRDADDR
. Устанавливает широковещательный адрес из элемента
ifr_broadaddr
.

SIOCGIFNETMASK
. Возвращает маску подсети в элементе
ifr_addr
.

SIOCSIFNETMASK
. Устанавливает маску подсети из элемента
ifr_addr
.

SIOCGIFMETRIC
. Возвращает метрику интерфейса в элементе
ifr_metric
. Метрика поддерживается ядром для каждого интерфейса, но используется демоном маршрутизации
routed
. Метрика интерфейса добавляется к счетчику количества переходов.

SIOCSIFMETRIC
. Устанавливает метрику интерфейса из элемента
ifr_metric
.

В этом разделе мы описали наиболее типичные операции интерфейсов. Во многих реализациях появились дополнительные операции.

17.8. Операции с кэшем ARP

Операции с кэшем ARP также осуществляются с помощью функции

ioctl
. В этих запросах используется структура
arpreq
, показанная в листинге 17.9 и определяемая в заголовочном файле
.

Листинг 17.9. Структура arpreq, используемая с вызовами ioctl для кэша ARP

struct arpreq {

 struct sockaddr arp_pa;    /* адрес протокола */

 struct sockaddr arp_ha;    /* аппаратный адрес */

 int             arp_flags; /* флаги */

};


#define ATF_INUSE 0x01 /* запись, которую нужно использовать */

#define ATF_COM   0x02 /* завершенная запись */

#define ATF_PERM  0x04 /* постоянная запись */

#define ATF_PUBL  0x08 /* опубликованная запись (отсылается другим узлам) */

Третий аргумент функции

ioctl
должен указывать на одну из этих структур. Поддерживаются следующие три вызова:

SIOCSARP
. Добавляет новую запись в кэш ARP или изменяет существующую запись.
arp_pa
— это структура адреса сокета Интернета, содержащая IP-адрес, a
arp_ha
 — это общая структура адреса сокета с элементом
ss_family
, равным
AF_UNSPEC
, и элементом
sa_data
, содержащим аппаратный адрес (например, 6-байтовый адрес Ethernet). Два флага
ATF_PERM
и
ATF_PUBL
могут быть заданы приложением. Два других флага,
ATF_INUSE
и
ATF_COM
, устанавливаются ядром.

SIOCDARP
. Удаляет запись из кэша ARP. Вызывающий процесс задает интернет-адрес удаляемой записи.

SIOCGARP
. Получает запись из кэша ARP. Вызывающий процесс задает интернет-адрес, и соответствующий адрес Ethernet возвращается вместе с флагами.

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

arp
.

ПРИМЕЧАНИЕ

Запросы функции ioctl, связанные с ARP, не поддерживаются в некоторых более новых системах, использующих для описанных операций ARP маршрутизирующие сокеты.

Обратите внимание, что невозможно с помощью функции

ioctl
перечислить все записи кэша ARP. Большинство версий команды
arp
при использовании флага
-a
(перечисление всех записей кэша ARP) считывают память ядра (
/dev/kmem
), чтобы получить текущее содержимое кэша ARP. Мы увидим более простой (и предпочтительный) способ, основанный на применении функции
sysctl
, описанной в разделе 18.4.

Пример: вывод аппаратного адреса узла

Теперь мы используем нашу функцию

my_addrs
для того, чтобы возвратить все IP-адреса узла. Затем для каждого IP-адреса мы делаем вызов
SIOCGARP
функции
ioctl
, чтобы получить и вывести аппаратные адреса. Наша программа показана в листинге 17.10.

Листинг 17.10. Вывод аппаратного адреса узла

//ioctl/prmac.c

 1 #include "unpifi.h"

 2 #include 


 3 int

 4 main(int argc, char **argv)

 5 {

 6  int sockfd;

 7  struct ifi_info *ifi;

 8  unsigned char *ptr;

 9  struct arpreq arpreq;

10  struct sockaddr_in *sin;


11  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

12  for (ifi = get_ifi_info(AF_INET, 0); ifi != NULL; ifi = ifi->ifi_next) {

13   printf("%s: ", Sock_ntop(ifi->ifi_addr, sizeof(struct sockaddr_in)));


14   sin = (struct sockaddr_in*)&arpreq.arp_pa;

15   memcpy(sin, ifi->ifi_addr, sizeof(struct sockaddr_in));


16   if (ioctl(sockfd, SIOCGARP, &arpreq) < 0) {

17    err_ret("ioctl SIOCGARP");

18    continue;

19   }


20   ptr = &arpreq.arp_ha.sa_data[0];

21   printf("%x:%x:%x:%x:%x:%x\n", *ptr, *(ptr+1),

22    *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5));

23  }

24  exit(0);

25 }

Получение списка адресов и проход в цикле по каждому из них

12
 Мы вызываем функцию
get_ifi_info
, чтобы получить IP-адреса узла, а затем выполняем цикл по всем адресам.

Вывод IP-адреса

13
 Мы выводим IP-адреса, используя функцию
inet_ntop
. Мы просим функцию
get_ifi_info
возвращать только адреса IPv4, так как ARP с IPv6 не используется.

Вызов функции ioctl и проверка ошибок

14-19
 Мы заполняем структуру
arp_pa
как структуру адреса сокета IPv4, содержащую адрес IPv4. Вызывается функция
ioctl
, и если она возвращает ошибку (например, указанный адрес относится к интерфейсу, не поддерживающему ARP), мы выводим сообщение и переходим к следующему адресу.

Вывод аппаратного адреса

20-22
 Выводится аппаратный адрес, возвращаемый
ioctl
.

При запуске этой программы на нашем узле

hpux
мы получаем:

hpux % prmac

192.6.38.100: 0:60:b0:c2:68:9b

192.168.1.1: 0:60:b0:b2:28:2b

127.0.0.1: ioctl SIOCGARP: Invalid argument

17.9. Операции с таблицей маршрутизации

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

ioctl
. Эти два вызова требуют, чтобы третий аргумент функции ioctl был указателем на структуру
rtentry
, которая определяется в заголовочном файле
. Обычно эти вызовы исходят от программы
route
. Их может делать только привилегированный пользователь. При наличии маршрутизирующих сокетов (глава 18) для выполнения этих запросов используются именно они, а не функция
ioctl
.

SIOCADDRT
. Добавить запись в таблицу маршрутизации.

SIOCDELRT
. Удалить запись из таблицы маршрутизации.

Нет способа с помощью функции

ioctl
перечислить все записи таблицы маршрутизации. Эту операцию обычно выполняет программа
netstat
с флагом
-r
. Программа получает таблицу маршрутизации, считывая память ядра (
/dev/kmem
). Как и в случае с просмотром кэша ARP, в разделе 18.4 мы увидим более простой (и предпочтительный) способ, предоставляемый функцией
sysctl
.

17.10. Резюме

Команды функции

ioctl
, используемые в сетевых приложениях, можно разделить на шесть категорий:

1. Операции с сокетами (находимся ли мы на отметке внеполосных данных?).

2. Операции с файлами (установить или сбросить флаг отсутствия блокировки).

3. Операции с интерфейсами (возвратить список интерфейсов, получить широковещательный адрес).

4. Операции с кэшем ARP (создать, изменить, получить, удалить).

5. Операции с таблицей маршрутизации (добавить или удалить).

6. Операции с потоками STREAMS (см. главу 31).

Мы будем использовать операции с сокетами и файлами, а получение списка интерфейсов — это настолько типичная операция, что для этой цели мы разработали собственную функцию. Мы будем применять ее много раз в оставшейся части книги. Вызовы функции

ioctl
с кэшем ARP и таблицей маршрутизации используются лишь несколькими специализированными программами.

Упражнения

1. В разделе 17.7 мы сказали, что широковещательный адрес, возвращаемый запросом SIOCGIFBRDADDR, возвращается в элементе

ifr_broadaddr
. Но на с. 173 [128] сказано, что он возвращается в элементе
ifr_dstaddr
. Имеет ли это значение?

2. Измените программу

get_ifi_info
так, чтобы она делала первый вызов
SIOCGIFCONF
для одной структуры
ifreq
, а затем каждый раз в цикле увеличивайте длину на размер одной из этих структур. Затем поместите в цикл операторы, которые выводили бы размер буфера при каждом вызове независимо от того, возвращает функция
ioctl
ошибку или нет, и при успешном выполнении выведите возвращаемую длину буфера. Запустите программу
prifinfo
и посмотрите, как ваша система обрабатывает вызов, когда размер буфера слишком мал. Выведите также семейство адресов для всех возвращаемых структур, семейство адресов которых не совпадает с указанным в первом аргументе функции
get_ifi_info
, чтобы увидеть, какие еще структуры возвращает ваша система.

3. Измените функцию

get_ifi_info
так, чтобы она возвращала информацию об адресе с альтернативным именем, если дополнительный адрес находится не в той подсети, в которой находится предыдущий адрес для данного интерфейса. Таким образом, наша версия из раздела 17.6 будет игнорировать альтернативные имена в диапазоне от 206.62.226.44 до 206.62.226.46, и это вполне нормально, поскольку они находятся в той же подсети, что и первичный адрес интерфейса 206.62.226.33. Но если альтернативное имя находится в другой подсети, допустим 192.3.4.5, возвратите структуру
ifi_info
с информацией о дополнительном адресе.

4. Если ваша система поддерживает вызов

SIOCGIGNUM
функции
ioctl
, измените листинг 17.4 так, чтобы запустить этот вызов, и используйте возвращаемое значение как начальный размер буфера.

Глава 18