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

Маршрутизирующие сокеты

18.1. Введение

Традиционно доступ к таблице маршрутизации Unix внутри ядра осуществлялся с помощью команд функции

ioctl
. В разделе 17.9 мы описали две операции:
SIOCADDRT
и
SIOCDELRT
, предназначенные для добавления и удаления маршрута. Мы также отметили, что не существует операции чтения всей таблицы маршрутизации — вместо этого программы, такие как
netstat
, считывают память ядра, для того чтобы получить содержимое таблицы маршрутизации. И еще одно добавление. Демонам маршрутизации, таким как
gated
, необходимо отслеживать сообщения ICMP (Internet Control Message Protocol — протокол управляющих сообщений Интернета) об изменении маршрутов, получаемых ядром, и для этого они часто создают символьный (неструктурированный) сокет ICMP (см. главу 28), а затем прослушивают на этом сокете все получаемые сообщения ICMP.

В 4.3BSD Reno интерфейс подсистемы маршрутизации ядра был упрощен за счет создания семейства адресов (домена)

AF_ROUTE
. Единственный тип сокетов, поддерживаемый для этого семейства, — это символьный сокет (raw socket). Маршрутизирующие сокеты поддерживают три типа операций.

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

2. Процесс может прочитать сообщение от ядра через маршрутизирующий сокет. Так ядро уведомляет процесс о том, что сообщение ICMP об изменении маршрутизации было получено и обработано.

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

3. Процесс может использовать функцию

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

Первые две операции требуют прав привилегированного пользователя, а третью операцию может выполнить любой процесс.

ПРИМЕЧАНИЕ

Некоторые версии Unix из более новых ослабили требование к правам пользователя для операции открытия маршрутизирующего сокета и ограничивают только передачу сообщений, изменяющих таблицу маршрутизации ядра. Это позволяет любому процессу узнать маршрут при помощи команды RTM_GET, не являясь суперпользователем.

Технически третья операция выполняется при помощи общей функции sysctl, а не маршрутизирующего сокета. Но мы увидим, что среди ее входных параметров есть семейство адресов (для описываемых в этой главе операций используется семейство AF_ROUTE), а результат она возвращает в том же формате, который используется ядром для маршрутизирующего сокета. Действительно, в ядре 4.4BSD обработка функции sysctl для семейства AF_ROUTE является частью кода маршрутизирующего сокета [128, с. 632–643].

Функция sysctl появилась в 4.4BSD. К сожалению, не все реализации, поддерживающие маршрутизирующие сокеты, предоставляют ее. Например, AIX 4.2, Digital Unix 4.0 и Solaris 2.6 поддерживают маршрутизирующие сокеты, но ни одна из этих систем не поддерживает утилиту sysctl.

18.2. Структура адреса сокета канального уровня

Структуры адреса сокета канального уровня будут встречаться нам как значения, содержащиеся в некоторых сообщениях, возвращаемых на маршрутизирующем сокете. В листинге 18.1[1] показано определение структуры, задаваемой в заголовочном файле

.

Листинг 18.1. Структура адреса сокета канального уровня

struct sockaddr_dl {

 uint8_t     sdl_len;

 sa_family_t sdl_family;   /* AF_LINK */

 uint16_t    sdl_index;    /* индекс интерфейса, присвоенный системой,

                              если > 0 */

 uint8_t     sdl_type;     /* тип интерфейса из .

 IFT_ETHER и т.д. */

 uint8_t     sdl_nlen;     /* длина имени, начинается с sdl_data[0] */

 uint8_t     sdl_alen;     /* длина адреса канального уровня */

 uint8_t     sdl_slen;     /* адрес селектора канального уровня */

 char        sdl_data[12]; /* минимальная рабочая область.

                              может быть больше; содержит имя

                              интерфейса и адрес канального уровня */

};

У каждого интерфейса имеется уникальный положительный индекс. Далее в этой главе мы увидим, каким образом он возвращается функциями

if_nametoindex
и
if_nameindex
. В главе 21 при обсуждении параметров многоадресных сокетов IPv6 и в главе 27 при обсуждении дополнительных параметров сокетов IPv6 и IPv4 мы вновь вернемся к этим функциям.

Элемент

sdl_data
содержит и имя, и адрес канального уровня (например, 48-разрядный MAC-адрес интерфейса Ethernet). Имя начинается с
sdl_data[0]
и не заканчивается нулем. Начало адреса канального уровня смещено на
sdl_nlen
байтов относительно начала имени. В этом заголовочном файле для возвращения указателя на адрес канального уровня задается следующий макрос:

#define LLADDR(s) ((caddr_t)((s)->sdl_data + (s)->sdl_nlen))

Эти структуры адреса сокета имеют переменную длину [128, с. 89]. Если адрес канального уровня и имя превышают 12 байт, размер структуры будет больше 20 байт. В 32-разрядных системах размер обычно округляется в большую сторону, до следующего числа, кратного 4 байтам. Мы также увидим на рис. 22.1, что когда одна из этих структур возвращается параметром сокета

IP_RECVIF
, все три длины становятся нулевыми, а элемента
sdl_data
не существует.

18.3. Чтение и запись

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

и показаны в табл. 18.1.


Таблица 18.1. Типы сообщений, проходящих по маршрутизирующему сокету

Тип сообщенияК ядру?От ядра?ОписаниеТип структуры
RTM_ADDДобавить маршрутrt_msghdr
RTM_CHANGEПоменять шлюз, метрику или флагиrt_msghdr
RTM_DELADDRАдрес был удален из интерфейсаifa_msghdr
RTM_DELETEУдалить маршрутrt_msghdr
RTM_GETСообщить о метрике и других характеристиках маршрутаrt_msghdr
RTM_IFINFOНаходится ли интерфейс в активном состоянииif_msghdr
RTM_LOCKБлокировка указанной метрикиrt_msghdr
RTM_LOSINGВозможно, неправильный маршрутrt_msghdr
RTM_MISSПоиск этого адреса завершился неудачноrt_msghdr
RTM_NEWSDDRАдрес добавлен к интерфейсуifa_msghdr
RTM_NEWMDDRГрупповой адрес добавлен к интерфейсуifma_msghdr
RTM_REDIRECTЯдро получило указание использовать другой маршрутrt_msghdr
RTM_RESOLVEЗапрос на определение адреса канального уровня по адресу получателяrt_msghdr

На маршрутизирующем сокете происходит обмен пятью различными структурами, как показано в последнем столбце таблицы:

rt_msghdr
,
if_msghdr
,
if_announcemsghdr
,
ifma_msghdr
и
ifa_msghdr
. Эти структуры представлены в листинге 18.2.


Листинг 18.2. Пять структур, возвращаемых с маршрутизирующими сообщениями

struct rt_msghdr { /* из  */

 u_short rtm_msglen;  /* для пропуска некорректных сообщений */

 u_char  rtm_version; /* для обеспечения двоичной совместимости в будущем */

 u_char  rtm_type;    /* тип сообщения */


 u_short rtm_index; /* индекс интерфейса, с которым связан адрес */

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

 int     rtm_addrs; /* битовая маска, идентифицирующая sockaddr (структуру адреса

                       сокета) в msg */

 pid_t   rtm_pid;   /* идентификация отправителя */

 int     rtm_seq;   /* для идентификации действия отправителем */

 int     rtm_errno; /* причина неудачного выполнения */

 int     rtm_use;   /* из rtentry */

 u_long  rtm_inits; /* какую метрику мы инициализируем */

 struct rt_metrics rtm_rmx; /* сами метрики */

};


struct if_msghdr { /* из  */

 u_short ifm_msglen;  /* для пропуска некорректных сообщений */

 u_char  ifm_version; /* для обеспечения двоичной совместимости в будущем */

 u_char  ifm_type;    /* тип сообщения */


 int     ifm_addrs;       /* как rtm_addrs */

 int     ifm_flags;       /* значение if_flags */

 u_short ifm_index;       /* индекс интерфейса, с которым связан адрес */

 struct if_data ifm_data; /* статистические и другие сведения */

};


struct ifa_msghdr { /* из  */

 u_short ifam_msglen;  /* для пропуска некорректных сообщений */

 u_char  ifam_version; /* для обеспечения двоичной совместимости в будущем */

 u_char  ifam_type;    /* тип сообщения */


 int     ifam_addrs;  /* как rtm_addrs */

 int     ifam_flags;  /* значение ifa_flags */

 u_short ifam_index;  /* индекс интерфейса, с которым связан адрес */

 int     ifam_metric; /* значение ifa_metric */

};


struct ifma_msghdr { /* из  */

 u_short ifmam_msglen;  /* для пропуска некорректных сообщений */

 u_char  ifmam_version; /* для обеспечения двоичной совместимости в будущем */

 u_char  ifmam_type;    /* тип сообщения */

 int     ifmam_addrs;   /* аналог rtm_addrs */

 int     ifmam_flags;   /* значение ifa_flags */

 u_short ifmam_index;   /* индекс связанного ifp */

};


struct if_announcemsghdr { /* из  */

 u_short ifan_msglen;  /* для пропуска некорректных сообщений */

 u_char  ifan_version; /* для обеспечения двоичной совместимости в будущем */

 u_char  ifan_type;    /* тип сообщения */

 u_short ifan_index;   /* индекс связанного ifp */

 char    ifan_name[IFNAMSIZ]; /* название интерфейса, напр. "en0" */

 u_short ifan_what;    /* тип объявления */

};

Первые три элемента каждой структуры одни и те же: длина, версия и тип сообщения. Тип — это одна из констант из первого столбца табл. 18.1. Элемент длины

xxx_msglen
позволяет приложению пропускать типы сообщений, которые оно не распознает.

Элементы

rtm_addrs
,
ifm_addrs
и
ifam_addrs
являются битовыми масками, указывающими, какая из возможных восьми структур адреса сокета следует за сообщением. В табл. 18.2 показаны константы и значения для битовой маски, определяемые в заголовочном файле
.


Таблица 18.2. Константы, используемые для ссылки на структуры адреса сокета в маршрутизирующих сообщениях

Битовая маска, константаБитовая маска, значениеИндекс массива, константаИндекс массива, значениеСтруктура адреса сокета содержит
RTA_DST0x01RTAX_DST0Адрес получателя
RTA_GATEWAY0x02RTAX_GATEWAY1Адрес шлюза
RTA_NETMASK0x04RTAX_NETMASK2Маска сети
RTA_GENMASK0x08RTAX_GENMASK3Маска клонирования
RTA_IFP0x10RTAX_IFP4Имя интерфейса
RTA_IFA0x20RTAX_IFA5Адрес интерфейса
RTA_AUTHOR0x40RTAX_AUTHOR6Отправитель запроса на перенаправление
RTA_BRD0x80RTAX_BRD7Адрес получателя типа «точка-точка» или широковещательный
RTAX_MAX8Максимальное количество элементов

В том случае, когда имеется множество структур адреса сокета, они всегда располагаются в порядке, показанном в таблице.

Пример: получение и вывод записи из таблицы маршрутизации

Теперь мы покажем пример использования маршрутизирующих сокетов. Наша программа получает аргумент командной строки, состоящий из адреса IPv4 в точечно-десятичной записи, и отправляет ядру сообщение

RTM_GET
для получения этого адреса. Ядро ищет адрес в своей таблице маршрутизации IPv4 и возвращает сообщение
RTM_GET
с информацией о соответствующей записи из таблицы маршрутизации. Например, если мы выполним на нашем узле
freebsd
такой код

freebsd # getrt 206.168.112.219

dest: 0.0.0.0

gateway: 12.106.32.1

netmask: 0.0.0.0

мы увидим, что этот адрес получателя использует маршрут по умолчанию (который хранится в таблице маршрутизации с IP-адресом получателя 0.0.0.0 и маской 0.0.0.0). Маршрутизатор следующей ретрансляции — это интернет-шлюз нашей системы. Если мы выполним

freebsd # getrt 192.168.42.0

dest: 192.168.42.0

gateway: AF_LINK, index=2

netmask: 255.255.255.0

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

sockaddr_dl
с индексом интерфейса 2.

Перед тем как представить исходный код, мы показываем на рис. 18.1, что именно мы пишем в маршрутизирующий сокет и что возвращает ядро.

Рис. 18.1. Обмен данными с ядром на маршрутизирующем сокете для команды RTM_GET

Мы создаем буфер, содержащий структуру

rt_msghdr
, за которой следует структура адреса сокета, содержащая адрес получателя, информацию о котором должно найти ядро. Тип сообщения (
rtm_type
) —
RTM_GET
, а битовая маска (
rtm_addrs
) —
RTA_DST
(вспомните табл. 18.2). Эти значения указывают, что структура адреса сокета, следующая за структурой
rt_msghdr
, — это структура, содержащая адрес получателя. Эта команда может использоваться с любым семейством протоколов (предоставляющим таблицу маршрутизации), поскольку семейство адресов, в которое входит искомый адрес, указано в структуре адреса сокета.

После отправки сообщения ядру мы с помощью функции

read
читаем ответ, формат которого показан на рис. 18.1 справа: структура
rt_msghdr
, за которой следует до четырех структур адреса сокета. Какая из четырех структур адреса сокета возвращается, зависит от записи в таблице маршрутизации. Мы сможем идентифицировать возвращаемую структуру адреса сокета по значению элемента
rtm_addrs
возвращаемой структуры
rt_msghdr
. Семейство каждой структуры адреса сокета указано в элементе
ss_family
, и как мы видели в наших предыдущих примерах, первый раз сообщение
RST_GET
содержало информацию о том, что адрес шлюза является структурой адреса сокета IPv4, а второй раз это была структура адреса сокета канального уровня.

В листинге 18.3 показана первая часть нашей программы.

Листинг 18.3. Первая часть программы, запускающая команду RTM_GET на маршрутизирующем сокете

//route/getrt.c

 1 #include "unproute.h"


 2 #define BUFLEN (sizeof(struct rt_msghdr) + 512)

 3 /* sizeof(struct sockaddr_in6) * 8 = 192 */

 4 #define SEQ 9999


 5 int

6 main(int argc, char **argv)

 7 {

 8  int sockfd;

 9  char *buf;

10  pid_t pid;

11  ssize_t n;

12  struct rt_msghdr *rtm;

13  struct sockaddr *sa, *rti_info[RTAX_MAX];

14  struct sockaddr_in *sin;


15  if (argc != 2)

16   err_quit("usage: getrt ");


17  sockfd = Socket(AF_ROUTE, SOCK_RAW, 0); /* необходимы права

                                привилегированного пользователя */


18  buf = Calloc(1, BUFLEN); /* инициализируется нулем */

19  rtm = (struct rt_msghdr*)buf;

20  rtm->rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in);

21  rtm->rtm_version = RTM_VERSION;

22  rtm->rtm_type = RTM_GET;

23  rtm->rtm_addrs = RTA_DST;

24  rtm->rtm_pid = pid = getpid();

25  rtm->rtm_seq = SEQ;


26  sin = (struct sockaddr_in*)(rtm + 1);

27  sin->sin_len = sizeof(struct sockaddr_in);

28  sin->sin_family = AF_INET;

29  Inet_pton(AF_INET, argv[1], &sin->sin_addr);


30  Write(sockfd, rtm, rtm->rtm_msglen);

31  do {

32   n = Read(sockfd, rtm, BUFLEN);

33  } while (rtm->rtm_type != RTM_GET || rtm->rtm_seq != SEQ ||

34   rtm->rtm_pid != pid);

1-3
 Наш заголовочный файл
unproute.h
подключает некоторые необходимые файлы, а затем включает наш файл
unp.h
. Константа
BUFLEN
— это размер буфера, который мы размещаем в памяти для хранения нашего сообщения ядру вместе с ответом ядра. Нам необходимо место для одной структуры
rt_msghdr
и, возможно, восьми структур адреса сокета (максимальное число, которое может возвратиться через маршрутизирующий сокет). Поскольку структура адреса сокета IPv6 имеет размер 28 байт, то значения 512 нам более чем достаточно.

Создание маршрутизирующего сокета

17
 Мы создаем символьный сокет в домене
AF_ROUTE
, что, как мы отмечали ранее, может потребовать прав привилегированного пользователя. Буфер размещается в памяти и инициализируется нулем.

Заполнение структуры rt_msghdr

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

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

26-29
 Следом за структурой
rt_msghdr
мы создаем структуру
sockaddr_in
, содержащую IPv4-адрес получателя, поиск которого будет проведен ядром в таблице маршрутизации. Все, что мы задаем — это длина адреса, семейство адреса и адрес.

Запись сообщения ядру (функция write) и чтение ответа (функция read)

30-34
 Мы передаем сообщение ядру с помощью функции
write
, и с помощью функции read читаем ответ. Поскольку у других процессов могут быть открытые маршрутизирующие сокеты, а ядро передает копию всех маршрутизирующих сообщений всем маршрутизирующим сокетам, мы должны проверить тип сообщения, порядковый номер и идентификатор процесса, чтобы узнать, что полученное сообщение — это ожидаемое нами сообщение.

Вторая часть этой программы показана в листинге 18.4. Она обрабатывает ответ.

Листинг 18.4. Вторая часть программы, запускающая команду RTM_GET на маршрутизирующем сокете

//route/getrt.c

35  rtm = (struct rt_msghdr*)buf;

36  sa = (struct sockaddr*)(rtm + 1);

37  get_rtaddrs(rtm->rtm_addrs, sa, rti_info);

38  if ((sa = rti_infо[RTAX_DST]) != NULL)

39   printf("dest: %s\n", Sock_ntop_host(sa, sa->sa_len));


40  if ((sa = rti_infо[RTAX_GATEWAY]) != NULL)

41   printf("gateway: %s\n", Sock_ntop_host(sa, sa->sa_len));

42  if ((sa = rti_info[RTAX_NETMASK]) != NULL)

43   printf("netmask: %s\n", Sock_masktop(sa, sa->sa_len));


44  if ((sa = rti_info[RTAX_GENMASK]) != NULL)

45   printf("genmask: %s\n", Sock_masktop(sa, sa->sa_len));


46  exit(0);

47 }

34-35
 Указатель
rtm
указывает на структуру
rt_msghdr
, а указатель
sa
— на первую следующую за ней структуру адреса сокета.

36
rtm_addrs
— это битовая маска той из возможных восьми структур адреса сокета, которая следует за структурой
rt_msghdr
. Наша функция
get_rtaddrs
(она показана в следующем листинге), получив эту маску и указатель на первую структуру адреса сокета (
sa
), заполняет массив
rti_info
указателями на соответствующие структуры адреса сокета. В предположении, что ядро возвращает все четыре структуры адреса сокета, показанные на рис. 18.1, полученный в результате массив
rti_info
будет таким, как показано на рис. 18.2.

Рис. 18.2. Структура rti_info, заполненная с помощью нашей функции get_rtaddrs

Затем наша программа проходит массив

rti_info
, делая все, что ей нужно, с непустыми указателями массива.

37-44
 Каждый из присутствующих четырех возможных адресов выводится. Мы вызываем нашу функцию
sock_ntop_host
для вывода адреса получателя и адреса шлюза, но для вывода двух масок подсети вызываем нашу функцию
sock_masktop
. Эту новую функцию мы покажем далее.

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

get_rtaddrs
, которую мы вызывали в листинге 18.4.

Листинг 18.5. Создание массива указателей на структуры адреса сокета в маршрутизирующем сообщении

//libroute/get_rtaddrs.c

 1 #include "unproute.h"


 2 /*

 3  * Округляем 'а' до следующего значения, кратного 'size'

 4  */

 5 #define ROUNDUP(a, size) (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))


 6 /* Переходим к следующей структуре адреса сокета.

 7  * Если sa_len равно 0, это значит, что

 8  * размер выражен числом типа u_long).

 9  */

10 #define NEXT_SA(ap) ар = (SA*) \

11  ((caddr_t)ар + (ap->sa_len ? ROUNDUP(ap->sa_len, sizeof(u_long)) : \

12  sizeof(u_long)))


13 void

14 get_rtaddrs(int addrs, SA *sa, SA **rti_info)

15 {

16  int i;


17  for (i = 0; i < RTAX_MAX; i++) {

18   if (addrs & (1 << i)) {

19    rti_info[i] = sa;

20    NEXT_SA(sa);

21   } else

22    rti_info[1] = NULL;

23  }

24 }

Цикл по восьми возможным указателям

Значение

RTAX_MAX
— максимальное число структур адреса сокета, возвращаемых от ядра в сообщении через маршрутизирующий сокет — равно 8. В цикле функции ведется поиск по каждой из восьми констант битовой маски
RTA_xxx
(см. табл. 18.2), которые могут быть присвоены элементам
rtm_addrs
,
ifm_addrs
и
ifam_addrs
структур, показанных в листинге 18.2. Если бит установлен, соответствующий элемент в массиве
rti_info
становится указателем на структуру адреса сокета; иначе элемент массива становится пустым указателем.

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

2-12
 Структуры адреса сокета имеют переменную длину, но в этом коде считается, что у каждой из них имеется поле
sa_len
, задающее длину структуры. Есть две сложности, с которыми придется столкнуться. Во-первых, маска подсети и маска клонирования могут возвращаться в структуре адреса сокета с нулевым значением поля
sa_len
, но на самом деле они занимают размер, представленный числом типа
unsigned long
(В главе 19 [128] обсуждается свойство клонирования таблицы маршрутизации 4.4BSD.) Это значение соответствует маске, состоящей только из нулевых битов, что мы видели в одном из приведенных выше примеров, когда для заданного по умолчанию маршрута маска подсети имела вид 0.0.0.0. Во-вторых, каждая структура адреса сокета может быть заполнена в конце таким образом, что следующая начнется на определенной границе, которая в данном случае соответствует значению типа
unsigned long
(например, 4-байтовая граница для 32-разрядной архитектуры). Хотя структуры
sockaddr
_in занимают 16 байт и не требуют заполнения, маски часто имеют в конце заполнение.

Последняя функция, которую мы покажем в примере нашей программы, — это функция

sock_masktop
, представленная в листинге 18.6, возвращающая строку для одного из двух возможных значений масок. Маски хранятся в структурах адреса сокета. Элемент
sa_family
не задан, но имеется элемент
sa_len
, принимающий значения 0, 5, 6, 7 или 8 для 32-битовых масок IPv4. Когда длина больше нуля, действительная маска начинается с того же смещения от начала структуры, что и адрес IPv4 в структуре
sockaddr_in
: 4 байта от начала структуры (как показано на рис. 18.21 [128]), что соответствует элементу
sa_data[2]
общей структуры адреса сокета.

Листинг 18.6. Преобразование значения маски к формату представления

//libroute/sock_masktop.c

 1 #include "unproute.h"


 2 const char*

 3 sock_masktop(SA *sa, socklen_t salen)

 4 {

 5  static char str[INET6_ADDRSTRLEN];

 6  unsigned char *ptr = &sa->sa_data[2];


 7  if (sa->sa_len == 0)

 8   return ("0.0.0.0");

 9  else if (sa->sa_len == 5)

10   snprintf(str, sizeof(str), '"%d.0.0.0", *ptr);

11  else if (sa->sa_len == 6)

12   snprintf(str, sizeof(str), "%d.%d.0.0", *ptr, *(ptr + 1));

13  else if (sa->sa_len == 7)

14   snprintf(str, sizeof(str), "%d.%d.%d.0", *ptr, *(ptr + 1), *(ptr + 2));

15  else if (sa->sa_len == 8)

16   snprintf(str, sizeof(str), "%d.%d.%d.%d",

17    *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3));

18  else

19  snprintf(str, sizeof(str), "(unknown mask, len = %d, family = %d)",

20   sa->sa_len, sa->sa_family);

21  return (str);

22 }

7-21
 Если длина равна нулю, то подразумевается маска 0.0.0.0. Если длина равна 5, хранится только первый байт 32-разрядной маски, а для оставшихся трех байтов подразумевается нулевое значение. Когда длина равна 8, хранятся все 4 байта маски.

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

write
на маршрутизирующем сокете сообщает нам, успешно ли была выполнена команда. Если это вся необходимая нам информация, мы вызываем функцию
shutdown
со вторым аргументом
SHUT_RD
, чтобы предотвратить отправку ответа. Например, если мы удаляем маршрут, то возвращение нуля функцией
write
означает успешное выполнение, а если удалить маршрут не удалось, возвращается ошибка
ESRCH
[128, с. 608]. Аналогично, когда добавляется маршрут, возвращение ошибки
EEXIST
при выполнении функции
write
означает, что запись уже существует. В нашем примере из листинга 18.3 функция
write
возвращает ошибку
ESRCH
, если записи в таблице маршрутизации не существует (допустим, у нашего узла нет заданного по умолчанию маршрута).

18.4. Операции функции sysctl

Маршрутизирующие сокеты нужны нам главным образом для проверки таблицы маршрутизации и списка интерфейсов при помощи функции

sysctl
. В то время как создание маршрутизирующего сокета (символьного сокета в домене
AF_ROUTE
) требует прав привилегированного пользователя, проверить таблицу маршрутизации и список интерфейсов с помощью функции
sysctl
может любой процесс.

#include 

#include 


int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,

 void *newp, size_t Inewlen);

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

Эта функция использует имена, похожие на имена базы управляющей информации (Management Information Base, MIB) простого протокола управления сетью (Simple Network Management Protocol, SNMP). В главе 25 [111] подробно описываются SNMP и его MIB. Эти имена являются иерархическими.

Аргумент

name
— это массив целых чисел, задающий имя, a
namelen
задает число элементов массива. Первый элемент массива определяет, какой подсистеме ядра направлен запрос. Второй элемент определяет некую часть этой подсистемы, и т.д. На рис. 18.3 показана иерархическая организация с некоторыми константами, используемыми на первых трех уровнях.

Рис. 18.3. Иерархическая организация имен функции sysctl

Для получения значений используется аргумент

oldp
. Он указывает на буфер, в котором ядро сохраняет значение. Аргумент
oldenp
имеет тип «значение-результат»: когда функция вызывается, значение, на которое указывает
oldenp
, задает размер этого буфера, а по завершении функции значением этого аргумента становится количество данных, сохраненных ядром в буфере. Если размера буфера недостаточно, возвращается ошибка
ENOMEM
. В специальном случае
oldp
может быть пустым указателем, a
oldenp
 — непустым указателем, и тогда ядро определяет, сколько данных возвратилось бы при вызове, сообщая это значение через
oldenp
.

Чтобы установить новое значение, используется аргумент

newp
, указывающий на буфер размера
newlen
. Если новое значение не задается,
newp
должен быть пустым указателем, a
newlen
должен быть равен нулю.

В руководстве (man) по применению функции

sysctl
подробно описывается различная системная информация, которую можно получить с помощью этой функции: информация о файловых системах, виртуальной памяти, ограничениях ядра, аппаратных характеристиках и т.д. Нас интересует сетевая подсистема, на которую указывает первый элемент массива name, равный
CTL_NET
(константы
CTL_xxx
определяются в заголовочном файле
). Тогда второй элемент может быть одним из перечисленных ниже.

AF_INET
. Получение или установка переменных, влияющих на протоколы Интернета. Следующий уровень с помощью одной из констант
IPROTO_xxx
задает протокол. BSD/OS 3.0 предоставляет на этом уровне около 30 переменных, управляющих такими свойствами, как генерация ядром переадресации ICMP, использование параметров TCP из RFC 1323, отправка контрольных сумм UDP и т.д. Пример подобного применения функции
sysctl
мы покажем в конце этого раздела.

AF_LINK
. Получение или установка информации канального уровня, такой как число интерфейсов PPP.

AF_ROUTE
. Возвращение информации либо о таблице маршрутизации, либо о списке интерфейсов. Мы вскоре опишем эту информацию.

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

Когда вторым элементом массива

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


Таблица 18.3. Информация функции sysctl, возвращаемая для маршрутизирующего домена

name[]Возвращает таблицуВозвращает кэш APR маршрутизацииВозвращает список интерфейсов
0CTL_NETCTL_NETCTL_NET
1AF_ROUTEAF_ROUTEAF_ROUTE
2000
3AF_INETAF_INETAF_INET
4NET_RT_DUMPNET_RT_FLAGSNET_RT_IFLIST
50RTF_LLINFO0

Поддерживаются три операции, задаваемые элементом

name[4]
. (Константы
NET_RT_xxx
определяются в заголовочном файле
.) Информация возвращается через указатель
oldp
при вызове функции
sysctl
. Этот буфер содержит переменное число сообщений
RTM_xxx
(см. табл. 18.1).

1. Операция

NET_RT_DUMP
возвращает таблицу маршрутизации для семейства адресов, заданного элементом
name[3]
. Если задано нулевое семейство адресов, возвращаются таблицы маршрутизации для всех семейств адресов.

Рис. 18.4. Информация возвращаемая функцией sysctl для команд CTL_NET и NET_RT_IFLIST

Таблица маршрутизации возвращается как переменное число сообщений

RTM_GET
, причем за каждым сообщением следует до четырех структур адреса сокета: получатель, шлюз, маска сети и маска клонирования записи в таблице маршрутизации. Пример такого сообщения мы показали в правой части рис. 18.1, а в нашем коде в листинге 18.4 проводится анализ одного из сообщений. В результате применения этой операции функции
sysctl
ядром возвращается одно или несколько таких сообщений.

2. Операция

NET_RT_FLAGS
возвращает таблицу маршрутизации для семейства адресов, заданного элементом
name[3]
, но учитываются только те записи таблицы маршрутизации, для которых значение флага
RTF_xxx
равно указанному в элементе
name[5]
. У всех записей кэша ARP в таблице маршрутизации установлен бит флага
RTF_LLINFO
.

Информация возвращается в том же формате, что и в предыдущем пункте.

3. Операция

NET_RT_IFLIST
возвращает информацию обо всех сконфигурированных интерфейсах. Если элемент
name[5]
ненулевой, это номер индекса интерфейса и возвращается информация только об этом интерфейсе. (Более подробно об индексах интерфейсов мы поговорим в разделе 18.6.) Все адреса, присвоенные каждому интерфейсу, также возвращаются, и если элемент
name[3]
ненулевой, возвращаются только адреса для семейства адресов, указанного в этом элементе.

Для каждого интерфейса возвращается по одному сообщению

RTM_IFINFO
, за которым следует одно сообщение
RTM_NEWADDR
для каждого адреса, заданного для интерфейса. За сообщением RTM_
IFINFO
следует по одной структуре адреса сокета канального уровня, а за каждым сообщением
RTM_NEWADDR
— до трех структур адреса сокета: адрес интерфейса, маска сети и широковещательный адрес. Эти два сообщения представлены на рис. 18.4.

Пример: определяем, включены ли контрольные суммы UDP

Теперь мы приведем простой пример использования функции

sysctl
с протоколами Интернета для проверки, включены ли контрольные суммы UDP. Некоторые приложения UDP (например, BIND) проверяют при запуске, включены ли контрольные суммы UDP, и если нет, пытаются включить их. Для того чтобы включить подобное свойство, требуются права привилегированного пользователя, но мы сейчас просто проверим, включено это свойство или нет. В листинге 18.7 представлена наша программа.

Листинг 18.7. Проверка включения контрольных сумм

//route/checkudpsum.c

 1 #include "unproute.h"

 2 #include 

 3 #include 

 4 #include  /* для констант UDPCTL_xxx */


 5 int

 6 main(int argc, char **argv)

 7 {

 8  int mib[4], val;

 9  size_t len;


10  mib[0] = CTL_NET;

11  mib[1] = AF_INET;

12  mib[2] = IPPROTO_UDP;

13  mib[3] = UDPCTL_CHECKSUM;


14  len = sizeof(val);

15  Sysctl(mib, 4, &val, &len, NULL, 0);

16  printf("udp checksum flag: %d\n", val);


17  exit(0);

18 }

Включение системных заголовков

2-4
 Следует включить заголовочный файл
, чтобы получить определение констант UDP функции
sysctl
. Для него требуются два других заголовка.

Вызов функции sysctl

10-16
 Мы размещаем в памяти массив целых чисел с четырьмя элементами и храним константы, соответствующие иерархии, показанной на рис. 18.3. Поскольку мы только получаем переменную и не присваиваем ей значение, аргумент
newp
функции
sysctl
мы задаем как пустой указатель, и поэтому аргумент
newp
этой функции имеет нулевое значение,
oldp
указывает на нашу целочисленную переменную, в которую сохраняется результат, a
oldenp
указывает на переменную типа «значение- результат», хранящую размер этого целого числа. Мы выводим либо 0 (отключено), либо 1 (включено).

18.5. Функция get_ifi_info (повтор)

Вернемся к примеру из раздела 17.6 — возвращение всех активных интерфейсов в виде связного списка структур

ifi_info
(см. листинг 17.2). Программа
prifinfo
остается без изменений (см. листинг 17.3), но теперь мы покажем версию функции
get_ifi_info
, использующую функцию
sysctl
вместо вызова
SIOCGIFCONF
функции
ioctl
в листинге 17.4.

Сначала в листинге 18.8 мы представим функцию

net_rt_iflist
. Эта функция вызывает функцию
sysctl
с командой
NET_RT_IFLIST
, чтобы возвратить список интерфейсов для заданного семейства адресов.

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

//libroute/net_rt_iflist.c

 1 #include "unproute.h"


 2 char*

 3 net_rt_iflist(int family, int flags, size_t *lenp)

 4 {

 5  int mib[6];

 6  char *buf;


 7  mib[0] = CTL_NET;

 8  mib[1] = AF_ROUTE;

 9  mib[2] = 0;

10  mib[3] = family; /* только адреса этого семейства */

11  mib[4] = NET_RT_IFLIST;

12  mib[5] = flags; /* индекс интерфейса или 0.*/

13  if (sysctl(mib, 6, NULL, lenp, NULL, 0) < 0)

14   return (NULL);


15  if ((buf = malloc(*lenp)) == NULL)

16   return (NULL);

17  if (sysctl(mib, 6, buf, lenp, NULL, 0) < 0) {

18   free(buf);

19   return (NULL);

20  }

21  return (buf);

22 }

7-14
 Инициализируется массив
mib
, как показано в табл. 18.3, для возвращения списка интерфейсов и всех сконфигурированных адресов заданного семейства. Затем функция
sysctl
вызывается дважды. В первом вызове функции третий аргумент нулевой, в результате чего в переменной, на которую указывает
lenp
, возвращается размер буфера, требуемый для хранения всей информации об интерфейсе.

15-21
 Затем в памяти выделяется место для буфера, и функция
sysctl
вызывается снова, на этот раз с ненулевым третьим аргументом. При этом переменная, на которую указывает
lenp
, содержит при завершении функции число, равное количеству информации, хранимой в буфере, и эта переменная размещается в памяти вызывающим процессом. Указатель на буфер также возвращается вызывающему процессу.

ПРИМЕЧАНИЕ

Поскольку размер таблицы маршрутизации или число интерфейсов может изменяться между двумя вызовами функции sysctl, значение, возвращаемое при первом вызове, содержит поправочный множитель 10% [128, с. 639-640].

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

get_ifi_info
.

Листинг 18.9. Функция get_ifi_info, первая половина

//route/get_ifi_info.c

 3 struct ifi_info *

 4 get_ifi_info(int family, int doaliases)

 5 {

 6  int flags;

 7  char *buf, *next, *lim;

 8  size_t len;

 9  struct if_msghdr *ifm;

10  struct ifa_msghdr *ifam;

11  struct sockaddr *sa, *rti_info[RTAX_MAX];

12  struct sockaddr_dl *sdl;

13  struct ifi_info *ifi, *ifisave, *ifihead, **ifipnext;

14  buf = Net_rt_iflist(family, 0, &len);

15  ifihead = NULL;

16  ifipnext = &ifihead;


17  lim = buf + len;

18  for (next = buf; next < lim; next += ifm->ifm_msglen) {

19   ifm = (struct if_msghdr*)next;

20   if (ifm->ifm_type = RTM_IFINFO) {

21    if (((flags = ifm->ifm_flags) & IFF_UP) == 0)

22     continue; /* игнорируем, если интерфейс не активен */


23    sa = (struct sockaddr*)(ifm + 1);

24    get_rtaddrs(ifm->ifm_addrs, sa, rti_info);

25    if ((sa = rti_info[RTAX_IFP]) != NULL) {

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

27     *ifipnext = ifi; /* предыдущий указатель указывал на эту

                           структуру */

28     ifipnext = &ifi->ifi_next; /* указатель на следующую структуру */


29     ifi->ifi_flags = flags;

30     if (sa->sa_family == AF_LINK) {

31      sdl = (struct sockaddr_dl*)sa;

32      ifi->ifi_index = sdl->sdl_index;

33      if (sdl->sdl_nlen > 0)

34       snprintf(ifi->ifi_name, IFI_NAME, "%*s",

35        sdl->sdl_nlen, &sdl->sdl_data[0]);

36      else

37       snprintf(ifi->ifi_name, IFI_NAME, "index %d",

38        sdl->sdl_index);


39      if ((ifi->ifi_hlen = sdl->sdl_alen) > 0)

40       memcpy(ifi->ifi_haddr, LLADDR(sdl),

41      min(IFI_HADDR, sdl->sdl_alen));

42     }

43    }

6-14
 Мы объявляем локальные переменные и затем вызываем нашу функцию
net_rt_iflist
.

17-19
 Цикл
for
— это цикл по всем сообщениям маршрутизации, попадающим в буфер в результате выполнения функции
sysctl
. Мы предполагаем, что сообщение — это структура
if_msghdr
, и рассматриваем поле
ifm_type
(вспомните, что первые три элемента трех структур идентичны, поэтому все равно, какую из трех структур мы используем для просмотра типа элемента).

Проверка, включен ли интерфейс

20-22
 Для каждого интерфейса возвращается структура
RTM_IFINFO
. Если интерфейс не активен, он игнорируется.

Определение, какие структуры адреса сокета присутствуют

23-24
sa
указывает на первую структуру адреса сокета, следующую за структурой
if_msghdr
. Наша функция get_rtaddrs инициализирует массив
rti_info
в зависимости от того, какие структуры адреса сокета присутствуют.

Обработка имени интерфейса

25-42
 Если присутствует структура адреса сокета с именем интерфейса, в памяти размещается структура
ifi_info
и хранятся флаги интерфейса. Предполагаемым семейством этой структуры адреса сокета является
AF_LINK
, что означает структуру адреса сокета канального уровня. Если элемент sdl_
nlen
ненулевой, имя интерфейса копируется в структуру
ifi_info
. В противном случае в качестве имени хранится строка, содержащая индекс интерфейса. Если элемент
sdl_alen
ненулевой, аппаратный адрес (например, адрес Ethernet) копируется в структуру
ifi_info
, а его длина также возвращается как
ifi_hlen
.

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

get_ifi_info
, которая возвращает IP-адреса для интерфейса.

Листинг 18.10. Функция get_ifi_info, вторая часть

//route/get_ifi_info.c

44   } else if (ifm->ifm_type == RTM_NEWADDR) {

45    if (ifi->ifi_addr) { /* уже имеется IP-адрес для интерфейса */

46     if (doaliases == 0)

47      continue;


48     /* у нас имеется новый IP-адрес для существующего интерфейса */

49     ifisave = ifi;

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

51     *ifipnext = ifi; /* предыдущий указатель указывал на эту

                           структуру */

52     ifipnext = &ifi->ifi_next; /* указатель на следующую структуру */

53     ifi->ifi_flags = ifi_save->ifi_flags;

54     ifi->ifi_index = ifisave->ifi_index;

55     ifi->ifi_hlen = ifisave->ifi_hlen;

56     memcpy(ifi->ifi_name, ifisave->ifi_name, IFI_NAME);

57     memcpy(ifi->ifi_haddr, ifisave->ifi_haddr, IFI_HADDR);

58    }


59    ifam = (struct ifa_msghdr*)next;

60    sa = (struct sockaddr*)(ifam + 1);

61    get_rtaddrs(ifam->ifam_addrs, sa, rti_info);


62    if ((sa = rti_infо[RTAX_IFA]) != NULL) {

63     ifi->ifi_addr = Calloc(1, sa->sa_len);

64     memcpy(ifi->ifi_addr, sa, sa->sa_len);

65    }


66    if ((flags & IFF_BROADCAST) && (sa = rti_infо[RTAX_BRD]) |= NULL) {

67     ifi->ifi_brdaddr = Calloc(1, sa->sa_len);

68     memcpy(ifi->ifi_brdaddr, sa, sa->sa_len);

69    }

70    if ((flags & IFF_POINTOPOINT) &&

71     (sa = rti_infо[RTAX_BRD]) != NULL) {

72     ifi->ifi_dstaddr = Calloc(1, sa->sa_len);

73     memcpy(ifi->ifi_dstaddr, sa, sa->sa_len);

74    }

75   } else

76    err_quit("unexpected message type %d", ifm->ifm_type);

77  }

78  /* "ifihead" указывает на первую структуру в связном списке */

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

80 }

Возвращение IP-адресов

44-65
 Сообщение
RTM_NEWADDR
возвращается функцией
sysctl
для каждого адреса, связанного с интерфейсом: для первичного адреса и для всех альтернативных имен (псевдонимов). Если мы уже заполнили IP-адрес для этого интерфейса, то мы имеем дело с альтернативным именем. Поэтому если вызывающему процессу нужен адрес псевдонима, мы должны выделить память для другой структуры
ifi_info
, скопировать заполненные поля и затем заполнить возвращенный адрес.

Возвращение широковещательного адреса и адреса получателя

66-75
 Если интерфейс поддерживает широковещательную передачу, возвращается широковещательный адрес, а если интерфейс является интерфейсом типа «точка-точка», возвращается адрес получателя.

18.6. Функции имени и индекса интерфейса

Документ RFC 3493 [36] определяет четыре функции, обрабатывающие имена и индексы интерфейсов. Эти четыре функции используются во многих случаях, когда необходимо описать интерфейс. Они были предложены в процессе разработки API IPv6 (главы 21 и 27), однако индексы интерфейсов имеются и в API IPv4 (например, в вызове

IP_RECVIF
или
AF_LINK
для маршрутизирующего сокета). Основной принцип, объявляемый в этом документе, состоит в том, что каждый интерфейс имеет уникальное имя и уникальный положительный индекс (нуль в качестве индекса никогда не используется).

#include 


unsigned int if_nametoindex(const char *ifname);

Возвращает: положительный индекс интерфейса в случае успешного выполнения, 0 в случае ошибки


char *if_indextoname(unsigned int ifindex, char *ifname);

Возвращает: указатель на имя интерфейса в случае успешного выполнения, NULL в случае ошибки


struct if_nameindex *if_nameindex(void);

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


void if_freenameindex(struct if_nameindex *Iptr);

Функция

if_nametoindex
возвращает индекс интерфейса, имеющего имя
ifname
. Функция
if_indextoname
возвращает указатель на имя интерфейса, если задан его индекс
ifindex
. Аргумент
ifname
указывает на буфер размера
IFNAMSIZ
(определяемый в заголовочном файле из листинга 17.1), который вызывающий процесс должен выделить для хранения результата, и этот указатель возвращается в случае успешного выполнения функции
if_indextoname
.

Функция

if_nameindex
возвращает указатель на массив структур
if_nameindex
:

struct if_nameindex {

 unsigned int if_index; /* 1, 2. ... */

 char *if_name; /* имя, завершаемое нулем: "le0", ... */

};

Последняя запись в этом массиве содержит структуру с нулевым индексом

if_index
и с пустым указателем
ifname
. Память для этого массива, а также для имен, на которые указывают элементы массива, выделяется динамически и освобождается при вызове функции
if_freenameindex
.

Теперь мы представим реализацию этих четырех функций с использованием маршрутизирующих сокетов.

Функция if_nametoindex

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

if_nametoindex
.

Листинг 18.11. Возвращение индекса интерфейса по его имени

//libroute/if_nametoindex.c

 1 #include "unpifi.h"

 2 #include "unproute.h"


 3 unsigned int

 4 if_nametoindex(const char *name)

 5 {

 6  unsigned int idx, namelen;

 7  char *buf, *next, *lim;

 8  size_t len;

 9  struct if_msghdr *ifm;

10  struct sockadd *sa, *rti_info[RTAX_MAX];

11  struct sockaddr_dl *sdl;


12  if ((buf = net_rt_iflist(0, 0, &len)) == NULL)

13   return(0);


14  namelen = strlen(name);

15  lim = buf + len;

16  for (next = buf; next < lim; next += ifm->ifm_msglen) {

17   ifm = (struct if_msghdr*)next;

18   if (ifm->ifm_type == RTM_IFINFO) {

19    sa = (struct sockaddr*)(ifm + 1);

20    get_rtaddrs(ifm->ifm_addrs, sa, rti_info);

21    if ((sa = rti_infо[RTAX_IFP]) != NULL) {

22     if (sa->sa_family == AF_LINK) {

23      sdl = (struct sockaddr_dl*)sa;

24      if (sdl->sdl_nlen == namelen

25       && strncmp(&sdl->sdl_data[0], name,

26       sdl->sdl_nlen) == 0) {

27       idx = sdl->sdl_index; /* сохранение перед

                                  вызовом free */

28       free(buf);

29       return(idx);

30      }

31     }

32    }


33   }

34  }

35  free(buf);

36  return(0); /* индекс для имени не найден */

37 }

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

12-13
Наша функция
net_rt_iflist
возвращает список интерфейсов.

Обработка только сообщений RTM_IFINFO

17-30
Мы обрабатываем сообщения в буфере (см. рис. 18.4) в поисках сообщений типа
RTM_IFINFO
. Найдя такое сообщение, мы вызываем нашу функцию
get_rtaddrs
, чтобы установить указатели на структуры адреса сокета, а если присутствует структура имени интерфейса (элемент
RTAX_IFP
массива
rti_info
), то имя интерфейса сравнивается с аргументом.

Функция if_indextoname

Следующая функция,

if_indextoname
, показана в листинге 18.12.

Листинг 18.12. Возвращение имени интерфейса по его индексу

libroute/if_indextoname.c

 1 #include "unpifi.h"

 2 #include "unproute.h"


 3 char*

 4 if_indextoname(unsigned int index, char *name)

 5 {

 6  char *buf, *next, *lim;

 7  size_t len;

 8  struct if_msghdr *ifm;

 9  struct sockaddr *sa, *rti_info[RTAX_MAX];

10  struct sockaddr_dl *sdl;


11  if ((buf = net_rt_iflist(0, index, &len)) == NULL)

12   return (NULL);


13  lim = buf + len;

14  for (next = buf; next < lim; next += ifm->ifm_msglen) {

15   ifm = (struct if_msghdr*)next;

16   if (ifm->ifm_type == RTM_IFINFO) {

17    sa = (struct sockaddr*)(ifm + 1);

18    get_rtaddrs(ifm->ifm_addrs, sa, rti_info);

19    if ((sa = rti_info[RTAX_IFP]) != NULL) {

20     if (sa->sa_family == AF_LINK) {

21      sdl = (struct sockaddr_dl*)sa;

22      if (sdl->sdl_index == index) {

23       int slen = min(IFNAMSIZ - 1, sdl->sdl_nlen);

24       strncpy(name, sdl->sdl_data, slen);

25       name[slen] = 0; /* завершающий нуль */

26       free(buf);

27       return (name);

28      }

29     }

30    }

31   }

32  }

33  free(buf);

34  return (NULL); /* нет соответствия индексу */

35 }

Эта функция практически идентична предыдущей, но вместо поиска имени интерфейса мы сравниваем индекс интерфейса с аргументом вызывающего процесса. Кроме того, второй аргумент нашей функции

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

Функция if_nameindex

Следующая функция,

if_nameindex
, возвращает массив структур
if_nameindex
, содержащих все имена интерфейсов и индексы. Она показана в листинге 18.13.

Листинг 18.13. Возвращение всех имен и индексов интерфейсов

/

/libroute/if_nameindex.c

 1 #include "unpifi.h"

 2 #include "unproute.h"


 3 struct if_nameindex*

 4 if_nameindex(void)

 5 {

 6  char *buf, *next, *lim;

 7  size_t len;

 8  struct if_msghdr *ifm;

 9  struct sockaddr *sa, *rti_info[RTAX_MAX];

10  struct sockaddr_dl *sdl;

11  struct if_nameindex *result, *ifptr;

12  char *namptr;


13  if ((buf = net_it_iflist(0, 0, &len)) == NULL)

14   return (NULL);


15  if ((result = malloc(len)) == NULL) /* завышенная оценка */

16   return (NULL);

17  ifptr = result;

18  namptr = (char*)result + len; /* имена начинаются с конца буфера */


19  lim = buf + len;

20  for (next = buf; next < lim; next += ifm->ifm_msglen) {

21   ifm = (struct if_msghdr*)next;

22   if (ifm->ifm_type == RTM_IFINFO) {

23    sa = (struct sockaddr*)(ifm + 1);

24    get_rtaddrs(ifm->ifm_addrs, sa, rti_info);

25    if ((sa = rti_infо[RTAX_IFP]) != NULL) {

26     if (sa->sa_family == AF_LINK) {

27      sdl = (struct sockaddr_in*)sa;

28      namptr -= sdl->sdl_nlen + 1;

29      strncpy(namptr, &sdl->sdl_data[0], sdl->sdl_nlen);

30      namptr[sdl->sdl_nlen] = 0; /* завершающий нуль */

31      ifptr->if_name = namptr;

32      ifptr->if_index = sdl->sdl_index;

33      ifptr++;

34     }

35    }

36   }

37  }

38  ifptr->if_name = NULL; /* отмечаем конец массива структур */

39  ifptr->if_index = 0;

40  free(buf);

41  return (result); /* вызывающий процесс должен освободить память

                        с помощью free(), когда все сделано */

43 }

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

13-18
 Мы вызываем нашу функцию
net_rt_iflist
для возвращения списка интерфейсов. Мы также используем возвращаемый размер в качестве размера буфера, который мы размещаем в памяти для записи массива возвращаемых структур
if_nameindex
. Оценка необходимого размера буфера несколько завышена, но это проще, чем проходить список интерфейсов дважды: один раз для подсчета числа интерфейсов и общего размера имен, а второй — для записи этой информации. Мы создаем массив
if_nameindex
в начале этого буфера и записываем имена интерфейсов, начиная с конца буфера.

Обработка только сообщений RTM_IFINFO

22-36
 Мы обрабатываем все сообщения, ища сообщения
RTM_IFINFO
и следующие за ними структуры адреса сокета. Имя и индекс интерфейса записываются в создаваемый нами массив.

Завершение массива

38-39
 Последняя запись в массиве имеет пустой указатель
if_name
и нулевой индекс.

Функция if_freenameindex

Последняя функция, показанная в листинге 18.13, освобождает память, которая была выделена для массива структур

if_nameindex
и хранящихся в нем имен.

Листинг 18.14. Освобождение памяти, выделенной функцией if_nameindex

43 void

44 if_freenameindex(struct if_nameindex *ptr)

45 {

46  free(ptr);

47 }

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

malloc
, то для освобождения памяти нам бы пришлось проходить через весь массив, освобождать память, выделенную для каждого имени, а затем удалять сам массив (используя функцию
free
).

18.7. Резюме

Последняя из структур адреса сокета, с которой мы встретились в книге, это

sockaddr_dl
— структура адреса сокета канального уровня, имеющая переменную длину. Ядра Беркли-реализаций связывают их с интерфейсами, возвращая в одной из этих структур индекс интерфейса, его имя и аппаратный адрес.

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

Функция

sysctl
предоставляет общий способ получения и хранения параметров операционной системы. При выполнении функции
sysctl
нас интересует получение следующей информации:

■ список интерфейсов;

■ таблица маршрутизации;

■ кэш ARP.

Изменения API сокетов, требуемые IPv6, включают четыре функции для сопоставления имен интерфейсов и их индексов. Каждому интерфейсу присваивается уникальный положительный индекс. В Беркли-реализациях с каждым интерфейсом уже связан индекс, поэтому нам несложно реализовать эти функции с помощью функции

sysctl
.

Упражнения

1. Что, как вы считаете, будет хранить поле

sdl_len
в структуре адреса сокета канального уровня для устройства с именем
eth10
, адрес канального уровня которого является 64-разрядным адресом IEEE EUI-64?

2. В листинге 18.3 отключите параметр сокета

SO_USELOOPBACK
перед вызовом функции
write
. Что происходит?

Глава 19