Чтение онлайн

ЖАНРЫ

UNIX: разработка сетевых приложений
Шрифт:

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

Поделиться с друзьями: