Мы создаем сокет 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);
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 байт) в качестве значения по умолчанию.