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

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

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

Теперь мы покажем пример использования маршрутизирующих сокетов. Наша программа получает аргумент командной строки, состоящий из адреса 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 <Ipaddress>");
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: %sn", Sock_ntop_host(sa, sa->sa_len));
40  if ((sa = rti_infо[RTAX_GATEWAY]) != NULL)
41   printf("gateway: %sn", Sock_ntop_host(sa, sa->sa_len));
42  if ((sa = rti_info[RTAX_NETMASK]) != NULL)
43   printf("netmask: %sn", Sock_masktop(sa, sa->sa_len));
44  if ((sa = rti_info[RTAX_GENMASK]) != NULL)
45   printf("genmask: %sn", 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, если записи в таблице маршрутизации не существует (допустим, у нашего узла нет заданного по умолчанию маршрута).

Оглавление книги


Генерация: 1.257. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз