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

Параметры IP

27.1. Введение

В IPv4 допускается, чтобы после фиксированного 20-байтового заголовка шли до 40 байт, отведенных под различные параметры. Хотя всего определено десять параметров, чаще всего используется параметр маршрута от отправителя (source route option). Доступ к этим параметрам осуществляется через параметр сокета

IP_OPTIONS
, что мы покажем именно на примере использования маршрутизации от отправителя.

В IPv6 допускается наличие расширяющих заголовков (extension headers) между фиксированным 40-байтовым заголовком IPv6 и заголовком транспортного уровня (например, ICMPv6, TCP или UDP). В настоящее время определены 6 различных расширяющих заголовков. В отличие от подхода, использованного в IPv4, доступ к расширяющим заголовкам IPv6 осуществляется через функциональный интерфейс, что не требует от пользователя понимания фактических деталей того, как именно эти заголовки расположены в пакете IPv6.

27.2. Параметры IPv4

На рис. А.1 мы показываем параметры, расположенные после 20-байтового заголовка IPv4. Как отмечено при рассмотрении этого рисунка, 4-разрядное поле длины ограничивает общий размер заголовка IPv4 до 15 32-разрядных слов (что составляет 60 байт), так что на параметры IPv4 остается 40 байт. Для IPv4 определено 10 различных параметров.

1. NOP (no-operation — нет действий). Этот однобайтовый параметр используется для выравнивания очередного параметра по 4-байтовой границе.

2. EOL (end-of-list — конец списка параметров). Этот однобайтовый параметр обозначает конец списка параметров. Поскольку суммарный размер параметров IP должен быть кратным 4 байтам, после последнего параметра добавляются байты EOL.

3. LSRR (Loose Source and Record Route — гибкая маршрутизация от отправителя с записью) (см. раздел 8.5 [111]). Пример использования этого параметра мы вскоре продемонстрируем.

4. SSRR (Strict Source and Record Route — жесткая маршрутизация от отправителя с записью) (см. раздел 8.5 [111]). Пример использования этого параметра мы также вскоре продемонстрируем.

5. Отметка времени (timestamp) (см. раздел 7.4 [111]).

6. Запись маршрута (record route) (см. раздел 7.3 [111]).

7. Основной параметр обеспечения безопасности (устаревший параметр) (basic security).

8. Расширенный параметр обеспечения безопасности (устаревший параметр) (extended security).

9. Идентификатор потока (устаревший параметр) (stream identifier).

10. Извещение маршрутизатора (router alert). Этот параметр описан в RFC 2113 [60]. Он включается в дейтаграмму IP, для того чтобы все пересылающие эту дейтаграмму маршрутизаторы обрабатывали ее содержимое.

В главе 9 книги [128] приводится более подробное рассмотрение первых шести параметров, а в указанных далее разделах [111] имеются примеры их использования.

Функции

getsockopt
и
setsockopt
(с аргументом level, равным
IPPROTO_IP
, а аргументом
optname
IP_OPTIONS
) предназначены соответственно для получения и установки параметров IP. Четвертый аргумент функций
getsockopt
и
setsockopt
 — это указатель на буфер (размер которого не превосходит 44 байт), а пятый аргумент — это размер буфера. Причина, по которой размер буфера может на 4 байт превосходить максимальный суммарный размер параметров, заключается в способе обработки параметров маршрута от отправителя, как мы вскоре увидим. Все остальные параметры помещаются в буфер именно в том виде, в котором они потом упаковываются в заголовок дейтаграммы.

Когда параметры IP задаются с использованием функции

setsockopt
, указанные параметры включаются во все дейтаграммы, отсылаемые с данного сокета. Этот принцип работает для сокетов TCP, UDP и для символьных сокетов. Для отмены какого-либо параметра следует вызвать функцию
setsockopt
и задать либо пустой указатель в качестве четвертого аргумента, либо нулевое значение в качестве пятого аргумента (длина).

ПРИМЕЧАНИЕ

Установка параметров IP для символьного сокета IP работает не во всех реализациях, если уже установлен параметр IP_HDRINCL (который мы обсудим в последующих главах). Многие Беркли-реализации не отсылают параметры, установленные с помощью IP_OPTIONS, если включен параметр IP_HDRINCL, так как приложение может устанавливать собственные параметры в формируемом им заголовке IP [128, с. 1056-1057]. В других системах (например, в FreeBSD) приложение может задавать свои параметры IP, либо используя параметр сокета IP_OPTIONS, либо установив параметр IP_HDRINCL и включив требуемые параметры в создаваемый им заголовок IP, но одновременное применение обоих этих способов не допускается.

При вызове функции

getsockopt
для получения параметров IP присоединенного сокета TCP, созданного функцией
accept
, возвращается лишь обращенный параметр маршрута от отправителя, полученный вместе с клиентским сегментом SYN на прослушиваемом сокете [128, с. 931]. TCP автоматически обращает маршрут от отправителя, поскольку маршрут, указанный клиентом, — это маршрут от клиента к серверу, а сервер должен использовать для отсылаемых им дейтаграмм обратный маршрут. Если вместе с сегментом SYN не был получен маршрут от отправителя, то значение пятого аргумента (этот аргумент типа «значение-результат», как было указано ранее, задает длину буфера) при завершении функции
getsockopt
будет равно нулю. Для прочих сокетов TCP, всех сокетов UDP и всех символьных сокетов IP при вызове функции
getsockopt
вы просто получите копию тех параметров IP, которые были установлены для этих сокетов с помощью функции
setsockopt
. Заметим, что для символьных сокетов IP полученный заголовок IP, включая все параметры IP, всегда возвращается всеми входными функциями, поэтому полученные параметры IP всегда доступны.

ПРИМЕЧАНИЕ

В Беркли-ядрах полученный маршрут от отправителя, так же как и другие параметры IP, никогда не возвращается для сокетов UDP. Показанный на с. 775 [128] код, предназначенный для получения параметров IP, существовал со времен 4.3BSD Reno, но так как он не работал, его всегда приходилось превращать в комментарий. Таким образом, для сокетов UDP невозможно использовать обращенный маршрут от отправителя полученной дейтаграммы, чтобы отослать ответ.

27.3. Параметры маршрута от отправителя IPv4

Маршрут от отправителя (source route) — это список IP-адресов, указанных отправителем дейтаграммы IP. Если маршрут является жестким (строгим, strict), то дейтаграмма должна передаваться только между указанными узлами и пройти их все. Иными словами, все узлы, перечисленные в маршруте от отправителя, должны быть соседними друг для друга. Но если маршрут является свободным, или гибким (loose), дейтаграмма должна пройти все перечисленные в нем узлы, но может при этом пройти и еще какие-то узлы, не входящие в список.

ПРИМЕЧАНИЕ

Маршрутизация от отправителя (source routing) в IPv4 является предметом споров и сомнений. В [20] пропагандируется отказ от поддержки этой функции на всех маршрутизаторах, и многие организации и провайдеры действительно следуют этому принципу. Один из наиболее разумных способов использования маршрутизации от отправителя — это обнаружение с помощью программы traceroute асимметричных маршрутов, как показано на с. 108–109 [111], но в настоящее время даже этот способ становится непопулярен. Тем не менее определение и получение маршрута от отправителя — это часть API сокетов, и поэтому заслуживает описания.

Параметры IPv4, связанные с маршрутизацией от отправителя, называются параметрами маршрутизации от отправителя с записью (Loose Source and Record Routes — LSRR в случае свободной маршрутизации и Strict Source and Record Routes — SSRR в случае жесткой маршрутизации), так как при проходе дейтаграммы через каждый из перечисленных в списке узлов происходит замена указанного адреса на адрес интерфейса для исходящих дейтаграмм. Это позволяет получателю дейтаграммы обратить полученный список, превратив его в маршрут, по которому будет послан ответ отправителю. Примеры этих двух маршрутов от отправителя вместе с соответствующим выводом программы

tcpdump
, приведены в разделе 8.5 книги [111].

Маршрут от отправителя мы определяем как массив адресов IPv4, которому предшествуют три однобайтовых поля, как показано на рис. 27.1. Это формат буфера, который передается функции

setsockopt
.

Рис. 27.1. Передача маршрута от отправителя ядру

Перед параметром маршрута от отправителя мы поместили параметр NOP (нет действий), чтобы все IP-адреса были выровнены по 4-байтовой границе. Это не обязательно, но желательно, поскольку в результате мы выравниваем адреса, не расходуя дополнительно лишней памяти (все IP-параметры обычно выравниваются, чтобы в итоге занимать место, кратное 4 байтам).

На рис. 27.1 показано, что маршрут состоит из 10 адресов, но первый приведенный адрес удаляется из параметра маршрута от отправителя и становится адресом получателя, когда дейтаграмма IP покидает узел отправителя. Хотя в 40-байтовом пространстве, отведенном под данный параметр IP, хватает места только для 9 адресов (не забудьте о 3-байтовом заголовке параметра, который мы вскоре опишем), фактически в заголовке IPv4 у нас имеется 10 IP-адресов, так как к 9 адресам узлов добавляется адрес получателя.

Поле

code
— это либо
0x83
для параметра LSRR, либо
0x89
для параметра SSRR. Задаваемое нами значение поля
len
— это размер параметра в байтах, включая 3-байтовый заголовок и дополнительный адрес получателя, приведенный в конце списка. Для маршрута, состоящего из одного IP-адреса, это значение будет равно 11, для двух адресов — 15, и т.д. вплоть до максимального значения 43. Параметр NOP не является частью обсуждаемого параметра, и его длина не включается в значение поля len, но она входит в размер буфера, который мы сообщаем функции
setsockopt
. Когда первый адрес в списке удаляется из параметра маршрута от отправителя и добавляется в поле адреса получателя в заголовок IP, значение поля
len
уменьшается на 4 (см. рис. 9.32 и 9.33 [128]). Поле
ptr
— это указатель, или сдвиг, задающий положение следующего IP-адреса из списка, который должен быть обработан. Мы инициализируем это поле значением 4, что соответствует первому адресу IP. Значение этого поля увеличивается на 4 каждый раз, когда дейтаграмма обрабатывается одним из перечисленных в маршруте узлов.

Теперь мы переходим к определению трех функций, с помощью которых мы инициализируем, создаем и обрабатываем параметр маршрута от отправителя. Наши функции предназначены для работы только с этим параметром. Хотя в принципе возможно объединить параметр маршрута от отправителя с другими параметрами IP (такими как параметр извещения маршрутизатора), но на практике параметры редко комбинируются. В листинге 27.1[1] приведена функция

inet_srcrt_init
, а также некоторые статические переменные, используемые при составлении параметра.

Листинг 27.1. Функция inet_srcrt_init: инициализация перед записью маршрута от отправителя

//ipopts/sourceroute.с

 1 #include "unp.h"

 2 #include 

 3 #include 


 4 static u_char *optr;   /* указатель на формируемые параметры */

 5 static u_char *lenptr; /* указатель на длину параметра SRR */

 6 static int    ocnt;    /* количество адресов */


 7 u_char*

 8 inet_srcrt_init(int type)

 9 {

10  optr = Malloc(44); /* NOP, код параметра. len, указатель + до 10

                          адресов */

11  bzero(optr, 44); /* гарантирует наличие EOL на конце */

12  ocnt = 0;

13  *optr++ = IPOPT_NOP; /* выравнивающие NOP */

14  *optr++ = type ? IPOPT_SSRR : IPOPT_LSRR;

15  lenptr = optr++; /* поле длины заполняется позже */

16  *optr++ = 4; /* сдвиг на первый адрес */


17  return(optr - 4); /* указатель для setsockopt() */

18 }

Инициализация

10-17
 Мы выделяем в памяти буфер, максимальный размер которого — 44 байт, и обнуляем его содержимое. Значение параметра EOL равно нулю, так что тем самым параметр инициализируется байтами EOL. Затем мы подготавливаем заголовок для маршрутизации от источника. Как показано на рис. 27.1, сначала мы обеспечиваем выравнивание при помощи параметра NOP, после чего указываем тип маршрута (гибкий, жесткий), длину и значение указателя. Мы сохраняем указатель в поле
len
. Это значение мы будем записывать при поочередном добавлении адресов к списку. Указатель на параметр возвращается вызывающему процессу, а затем передается как четвертый аргумент функции
setsockopt
.

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

inet_srcrt_add
, добавляет один IPv4-адрес к создаваемому маршруту от отправителя.

Листинг 27.2. Функция inet_srcrt_add: добавление одного IPv4-адреса к маршруту от отправителя

//ipopts/sourceroute.с

19 int

20 inet_srcrt_add(char *hostptr)

21 {

22  int len;

23  struct addrinfo *ai;

24  struct sockaddr_in *sin;


25  if (ocnt > 9)

26   err_quit("too many source routes with: %s", hostptr);


27  ai = Host_serv(hostptr, NULL, AF_INET, 0);

28  sin = (struct sockaddr_in*)ai->ai_addr;

29  memcpy(optr, &sin->sin_addr, sizeof(struct in_addr));

30  freeaddrinfo(ai);


31  optr += sizeof(struct in_addr);

32  ocnt++;

33  len = 3 + (ocnt * sizeof(struct in_addr));

34  *lenptr = len;

35  return(len + 1); /* размер для setsockopt() */

36 }

Аргумент

19-20
 Аргумент функции указывает либо на имя узла, либо на адрес IP в точечно- десятичной записи.

Проверка переполнения

25-26
 Мы проверяем количество переданных адресов и выполняем инициализацию, если обрабатывается первый адрес.

Получение двоичного IP-адреса и запись маршрута

29-37
 Функция
host_serv
обрабатывает имя узла или его IP-адрес, а возвращаемый ей адрес в двоичной форме мы помещаем в список. Мы обновляем поле
len
и возвращаем полный размер буфера (с учетом параметров NOP), который вызывающий процесс затем передаст функции
setsockopt
.

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

getsockopt
, формат этого параметра отличается от того, что было показано на рис. 27.1. Формат полученного параметра маршрута от отправителя показан на рис. 27.2.

Рис. 27.2. Формат параметра маршрута от отправителя, возвращаемого функцией getsockopt

В первую очередь, мы можем отметить, что порядок следования адресов изменен ядром на противоположный относительно полученного маршрута от отправителя. Имеется в виду следующее: если в полученном маршруте содержались адреса А, В, С и D в указанном порядке, то под противоположным порядком подразумевается следующий: D, С, В, А. Первые 4 байта содержат первый IP-адрес из списка, затем следует однобайтовый параметр NOP (для выравнивания), затем — 3-байтовый заголовок параметра маршрута от отправителя, и далее остальные IP-адреса. После 3-байтового заголовка может следовать до 9 IP-адресов, и максимальное значение поля

len
в возвращенном заголовке равно 39. Поскольку параметр NOP всегда присутствует, длина буфера, возвращаемая функцией
getsockopt
, всегда будет равна значению, кратному 4 байтам.

ПРИМЕЧАНИЕ

Формат, приведенный на рис. 27.2, определен в заголовочном файле в виде следующей структуры:

#define MAX_IPOPTLEN 40


struct ipoption {

 struct in_addr ipopt_dst; /* адрес первого получателя */

 char   ipopt_list[MAX_IPOPTLEN]; /* соответствующие параметры */

};

В листинге 27.3 мы анализируем эти данные, не используя указанную структуру.

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

setsockopt
. Если нам было бы нужно преобразовать формат, показанный на рис. 27.2, к формату, показанному на рис. 27.1, нам следовало бы поменять местами первые и вторые 4 байта и изменить значение поля
len
, добавив к имеющемуся значению 4. К счастью, нам не нужно этого делать, так как Беркли-реализации автоматически используют обращенный маршрут от получателя для сокета TCP. Иными словами, данные, возвращаемые функцией
getsockopt
(представленные на рис. 27.2), носят чисто информативный характер. Нам не нужно вызывать функцию
setsockopt
, чтобы указать ядру на необходимость использования данного маршрута для дейтаграмм IP, отсылаемых по соединению TCP, — ядро сделает это само. Подобный пример с нашим сервером TCP мы вскоре увидим.

Следующей из рассматриваемых нами функций, связанных с параметром маршрутизации, полученный маршрут от отправителя передается в формате, показанном на рис. 27.2. Затем она выводит соответствующую информацию. Эту функцию

inet_srtcrt_print
мы показываем в листинге 27.3.

Листинг 27.3. Функция inet_srtcrt_print: вывод полученного маршрута от отправителя

//ipopts/sourceroute.c

37 void

38 inet_srcrt_print(u_char *ptr, int len)

39 {

40  u_char c;

41  char str[INET_ADDRSTRLEN];

42  struct in_addr hop1;


43  memcpy(&hop1, ptr, sizeof(struct in_addr));

44  ptr += sizeof(struct in_addr);


45  while ((c = *ptr++) == IPOPT_NOP); /* пропуск параметров NOP */


46  if (с == IPOPT_LSRR)

47   printf("received LSRR: ");

48  else if (c == IPOPT_SSRR)

49   printf("received SSRR: ");

50  else {

51   printf("received option type %d\n", c);

52   return;

53  }

54  printf("%s ", Inet_ntop(AF_INET, &hop1, str, sizeof(str)));


55  len = *ptr++ - sizeof(struct in_addr); /* вычитаем адрес получателя */

56  ptr++; /* пропуск указателя */

57  while (len > 0) {

58   printf("%s ", Inet_ntop(AF_INET, ptr, str, sizeof(str)));

59   ptr += sizeof(struct in_addr);

60   len -= sizeof(struct in_addr);

61  }

62  printf("\n");

63 }

Сохраняем первый адрес IP, пропускаем все параметры NOP

43-45
 Первый IP-адрес в буфере сохраняется, а все следующие за ним параметры NOP мы пропускаем.

Проверяем параметр маршрута от отправителя

46-62
 Мы выводим информацию о маршруте и проверяем значение поля
code
, содержащегося в 3-байтовом заголовке, получаем значение поля
len
и пропускаем указатель
ptr
. Затем мы выводим все IP-адреса, следующие за 3-байтовым заголовком, кроме IP-адреса получателя.

Пример

Теперь мы модифицируем наш эхо-сервер TCP таким образом, чтобы выводить полученный маршрут от отправителя, а эхо-клиент TCP — так, чтобы маршрут от отправителя можно было задавать. В листинге 27.4 показан код эхо-клиента TCP.

Листинг 27.4. Эхо-клиент TCP, задающий маршрут от отправителя

//ipopts/tcpcli01.c

 1 #include "unp.h"


 2 int

 3 main(int argc, char **argv)

 4 {

 5  int c, sockfd, len = 0;

 6  u_char *ptr = NULL;

 7  struct addrinfo *ai;


 8  if (argc < 2)

 9   err_quit("usage: tcpcli01 [ -[gG]  ... ] ");


10  opterr = 0; /* отключаем запись сообщений getopt() в stderr */

11  while ((с = getopt(argc, argv, "gG")) != -1) {

12   switch (c) {

13   case 'g': /* свободный маршрут от отправителя */

14    if (ptr)

15     err_quit("can't use both -g and -G");

16    ptr = inet_srcrt_init(0); 

17    break;


18   case 'G': /* жесткий маршрут от отправителя */

19    if (ptr)

20     err_qint("can't use both -g and -G");

21    ptr = inet_srcrt_init(1);

22    break;


23   case '?':

24    err_quit("unrecognized option: %c", c);

25   }

26  }


27  if (ptr)

28   while (optind < argc-1)

29    len = inet_srcrt_add(argv[optind++]);

30  else if (optind < argc-1)

31   err_quit("need -g or -G to specify route");


32  if (optind != argc-1)

33   err_quit("missing ");


34  ai = Host_serv(argv[optind], SERV_PORT_STR, AF_INET, SOCK_STREAM);


35  sockfd = Socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);


36  if (ptr) {

37   len = inet_srcrt_add(argv[optind]); /* получатель в конце */

38   Setsockopt(sockfd, IPPROTO_IP, IP_OPTIONS, ptr, len);

39   free(ptr);

40  }


41  Connect(sockfd, ai->ai_addr, ai->ai_addrlen);


42  str_cli(stdin, sockfd); /* вызов рабочей функции */


43  exit(0);

44 }

Обработка аргументов командной строки

12-26
 Мы вызываем нашу функцию
inet_srcrt_init
, чтобы инициализировать маршрут от отправителя. Тип маршрутизации указывается при помощи параметра
-g
(свободная) или
-G
(жесткая).

27-33
 Если указатель
ptr
установлен, значит, был указан параметр маршрутизации от отправителя, и все указанные промежуточные узлы добавляются к маршруту, подготовленному на предыдущем этапе функцией
inet_srcrt_add
. Если же
ptr
не установлен, но в командной строке еще есть аргументы, значит, пользователь задал маршрут, но не указал его тип. В этом случае программа завершает работу с сообщением об ошибке.

Обработка адреса получателя и создание сокета

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

36-42
 Если маршрут от отправителя задан, следует добавить IP-адрес сервера в конец списка адресов (см. рис. 27.1). Функция
setsockopt
устанавливает маршрут от отправителя для данного сокета. Затем мы вызываем функцию connect, а потом — нашу функцию
str_cli
(см. листинг 5.4).

Наш TCP-сервер имеет много общего с кодом, показанным в листинге 5.9, но содержит следующие изменения.

Во-первых, мы выделяем место для параметров:

int len;

u_char *opts;


opts = Malloc(44);

Во-вторых, мы получаем параметры IP после вызова функции

accept
, но перед вызовом функции
fork
:

len = 44;

Getsockopt(connfd, IPPROTO_IP, IP_OPTIONS, opts, &len);

if (len > 0) {

 printf("received IP options, len = %d\n", len);

 inet_srcrt_print(opts, len);

}

Если сегмент SYN, полученный от клиента, не содержит никаких параметров IP, переменная

len
по завершении функции
getsockopt
будет иметь нулевое значение (эта переменная относится к типу «значение-результат»). Как уже упоминалось, нам не нужно предпринимать какие-либо шаги для того, чтобы на стороне сервера использовался обращенный маршрут от отправителя: это делается автоматически без нашего участия [128, с. 931]. Вызывая функцию
getsockopt
, мы просто получаем копию обращенного маршрута от отправителя. Если мы не хотим, чтобы TCP использовал этот маршрут, то после завершения функции accept следует вызвать функцию
setsockopt
и задать нулевую длину (последний аргумент), тем самым удалив все используемые в текущий момент параметры IP. Но маршрут от отправителя тем не менее уже был использован в процессе трехэтапного рукопожатия при пересылке второго сегмента. Если мы уберем параметры маршрутизации, IP составит и будет использовать для пересылки последующих пакетов какой-либо другой маршрут.

Теперь мы покажем пример клиент-серверного взаимодействия при заданном маршруте от отправителя. Мы запускаем наш клиент на узле

freebsd
следующим образом:

freebsd4 % tcpcli01 -g macosx freebsd4 macosx

Тем самым дейтаграммы IP отсылаются с узла

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

Когда соединение устанавливается, на стороне сервера выдается следующий результат:

macosx % tcpserv01

received IP options, len = 16

received LSRR, 172.24.37.94 172.24.37.78 172.24.37.94

Первый выведенный IP-адрес — это первый узел обратного маршрута (

freebsd4
, как показано на рис. 27.2), а следующие два адреса идут в том порядке, который используется сервером для отправки дейтаграмм назад клиенту. Если мы понаблюдаем за процессом взаимодействия клиента и сервера с помощью программы
tcpdump
, мы увидим, как используется параметр маршрутизации для каждой дейтаграммы в обоих направлениях.

ПРИМЕЧАНИЕ

К сожалению, действие параметра сокета IP_OPTIONS никогда не было документировано, поэтому вы можете увидеть различные вариации поведения в системах, не происходящих от исходного кода Беркли. Например, в системе Solaris 2.5 первый адрес, возвращаемый функцией getsockopt (см. рис. 27.2) — это не первый адрес в обращенном маршруте, а адрес собеседника. Тем не менее обратный маршрут, используемый TCP, будет корректен. Кроме того, в Solaris 2.5 всем параметрам маршрутизации предшествует четыре параметра NOP, что ограничивает параметр маршрутизации восемью IP-адресами, а не девятью, которые реально могли бы поместиться.

Уничтожение полученного маршрута от отправителя

К сожалению, использование параметра маршрутизации образует брешь в системе обеспечения безопасности программ, выполняющих аутентификацию по IP-адресам (сейчас такая проверка считается недостаточной). Если хакер отправляет пакеты, используя один из доверенных адресов в качестве адреса отправителя, но указывая в качестве одного из промежуточных адресов маршрута от отправителя свой собственный адрес, возвращаемые по обратному маршруту пакеты будут попадать к хакеру, а «отправитель», чьим адресом хакер прикрывался, никогда не узнает об этом. Начиная с выпуска Net/1 (1989), серверы

rlogind
и
rshd
использовали код, аналогичный следующему:

u_char buf[44];

char lbuf[BUFSIZ];

int optsize;


optsize = sizeof(buf);

if (getsockopt(0, IPPROTO_IP, IP_OPTIONS,

 buf, &optsize) == 0 && optsize != 0) {

 /* форматируем параметры как шестнадцатеричные числа для записи в lbuf[] */

 syslog(LOG_NOTICE,

  "Connection received using IP options (ignored):%s", lbuf);

 setsockopt(0, ipproto, IP_OPTIONS, NULL, 0);

}

Если устанавливается соединение с какими-либо параметрами IP (значение переменной

optsize
, возвращенное функцией
getsockopt
, не равно нулю), то с помощью функции
syslog
делается запись соответствующего сообщения и вызывается функция
setsockopt
для очистки всех параметров. Таким образом предотвращается отправка последующих сегментов TCP для данного соединения по обращенному маршруту от отправителя. Сейчас уже известно, что этой технологии недостаточно, так к моменту установления соединения трехэтапное рукопожатие TCP будет уже завершено и второй сегмент (сегмент SYN-ACK на рис. 2.5) будет уже отправлен по обращенному маршруту от отправителя к клиенту. Даже если этот сегмент не успеет дойти до клиента, то во всяком случае он дойдет до некоторого промежуточного узла, входящего в маршрут от отправителя, где, возможно, затаился хакер. Так как предполагаемый хакер видел порядковые номера TCP в обоих направлениях, даже если никаких других пакетов по маршруту от отправителя послано не будет, он по-прежнему сможет отправлять серверу сообщения с правильным порядковым номером.

Единственным решением этой возможной проблемы является запрет на прием любых соединений TCP, приходящих по обращенному маршруту от отправителя, когда вы используете IP-адрес от отправителя для какой-либо формы подтверждения (как, например, в случае с

rlogin
или
rshd
). Вместо вызова функции
setsockopt
во фрагменте кода, приведенном ранее, закройте только что принятое соединение и завершите только что порожденный процесс сервера. Второй сегмент трехэтапного рукопожатия отправится, но соединение не останется открытым и не будет использоваться далее.

27.4. Заголовки расширения IPv6

Мы не показываем никаких параметров в заголовке IPv6 на рис. А.2 (который всегда имеет длину 40 байт), но следом за этим заголовком могут идти заголовки расширения[7] (extension headers).

1. Параметры для транзитных узлов (hop-by-hop options) должны следовать непосредственно за 40-байтовым заголовком IPv6. В настоящее время не определены какие-либо параметры для транзитных узлов, которые могли бы использоваться в приложениях.

2. Параметры получателя (destination options). В настоящее время не определены какие-либо параметры получателя, которые могли бы использоваться в приложениях.

3. Заголовок маршрутизации. Этот параметр маршрутизации от отправителя аналогичен по своей сути тем, которые мы рассматривали в случае IPv4 в разделе 27.3.

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

5. Заголовок аутентификации (АН — authentication header). Использование этого заголовка документировано в RFC 2402 [65].

6. Заголовок шифрования (ESH — encapsulating security payload header). Использование этого заголовка документировано в RFC 2406 [66].

Мы уже говорили о том, что заголовок фрагментации целиком обрабатывается ядром, как и заголовки АН и ESP, обработка которых управляется согласно базе данных соглашений о безопасности (о сокетах управления ключами читайте в главе 9). Остаются еще три параметра, которые мы обсудим в следующем разделе. Интерфейс этих параметров определен в RFC 3542 [114].

27.5. Параметры транзитных узлов и параметры получателя IPv6

Параметры для транзитных узлов и параметры получателя IPv6 имеют одинаковый формат, показанный на рис. 27.3. Восьмиразрядное поле следующий заголовок (next header) идентифицирует следующий заголовок, который следует за данным заголовком. Восьмиразрядное поле длина заголовка расширения (header extension length) содержит длину заголовка расширения в условных единицах (1 у.e. = 8 байт), но не учитывает первые 8 байт заголовка. Например, если заголовок занимает всего 8 байт, то значение поля длины будет равно нулю. Если заголовок занимает 16 байт, то соответственно значение этого поля будет равно 1, и т.д. Оба заголовка заполняются таким образом, чтобы длина каждого была кратна 8 байтам. Это достигается либо с помощью параметра

pad1
, либо с помощью параметра
padN
, которые мы вскоре рассмотрим.

Рис. 27.3. Формат параметра для транзитных узлов и параметра получателя

Заголовок параметра транзитных узлов и заголовок параметра получателя могут содержать произвольное количество отдельных параметров, как показано на рис. 27.4.

Рис. 27.4. Формат отдельных параметров, входящих в заголовок параметра транзитных узлов и заголовок параметра получателя

Этот формат иногда называется TLV, так как для каждого отдельного параметра указывается его тип, длина и значение (type, length, value). Восьмиразрядное поле типа (type) указывает тип параметра. В дополнение к этому два старших разряда указывают, что именно узел IPv6 будет делать с этим параметром в том случае, если он не сможет в нем разобраться:

■ 00 — пропустить параметр и продолжить обработку заголовка.

■ 01 — игнорировать пакет.

■ 10 — игнорировать пакет и отослать отправителю сообщение об ошибке ICMP типа 2 (см. табл. А.6), независимо от того, является ли адрес получателя пакета групповым адресом.

■ 11 — игнорировать пакет и отослать отправителю сообщение об ошибке ICMP типа 2 (см. табл. А.6) но только в том случае, если адрес получателя пакета не является адресом многоадресной передачи.

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

■ 0 — данные параметра не могут быть изменены.

■ 1 — данные параметра могут быть изменены.

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

8-разрядное поле длины задает длину данных этих параметров в байтах. Длина поля типа и длина самого поля длины не входят в это значение.

Два параметра заполнения (pad options) определены в RFC 2460 [27] и могут быть использованы как в заголовке параметров для транзитных узлов, так и в заголовке параметров получателя. Один из параметров транзитных узлов — параметр размера увеличенного поля данных (jumbo pay load length option) — определен в RFC 2675 [9]. Ядро генерирует этот параметр по мере необходимости и обрабатывает при получении. Новый параметр увеличенного объема данных для IPv6, аналогичный параметру извещения маршрутизатора (router alert), описан в RFC 2711 [87]. Эти параметры изображены на рис. 27.5. Есть и другие параметры (например, для Mobile-IPv6), но мы их на рисунке не показываем.

Рис. 27.5. Параметры IPv6 для транзитных узлов

Параметр

pad1
— это единственный параметр, для которого не указывается длина и значение. Его назначение — вставка одного пустого байта для заполнения. Параметр
padN
используется, когда требуется вставить 2 или более байта заполнения. Для 2 байт заполнения длина параметра будет иметь нулевое значение, а сам параметр будет состоять из поля типа и поля длины. В случае 3 байт заполнения длина будет равна 1, а следом за полем длины будет стоять один нулевой байт. Параметр размера увеличенного поля данных допускает увеличение поля размера дейтаграмм до 32 бит и используется, когда 16-разрядное поле размера, показанное на рис. А.2, оказывается недостаточно большим.

Мы показываем эти параметры схематически, потому что для всех параметров получателя и транзитных узлов действует так называемое условие выравнивания (alignment requirement), записываемое как xn + y. Это означает, что сдвиг данного параметра относительно начала заголовка равен числу, n раз кратному x байтам, к которому добавлено у байтов (то есть величина сдвига в байтах равна xn + y). Например, условие выравнивания для параметра размера увеличенного поля данных записывается как 4n + 2. Это означает, что 4-байтовое значение параметра (длина размера увеличенного поля данных) будет выровнено по 4-байтовой границе. Причина, по которой значение y для этого параметра равно 2, заключается в том, что параметры транзитных узлов и получателя начинаются именно с двух байтов — один байт используется для указания типа, другой — для указания длины (см. рис. 27.4). Для параметра уведомления маршрутизатора условие выравнивания записывается как 2+ 0, благодаря чему 2-байтовое значение параметра оказывается выровненным по 2-байтовой границе.

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

sendmsg
и возвращаются функцией
recvmsg
также в виде вспомогательных данных. От приложения не требуется никаких специальных действий для отправки этих параметров — нужно только задать их при вызове функции
sendmsg
. Но для получения этих параметров должен быть включен соответствующий параметр сокета:
IPV6_RECVHOPOPTS
для параметра транзитных узлов и
IPV6_RECVDSTOPTS
для параметров получателя. Например, чтобы можно было получить оба параметра, нужен следующий код:

const int on = 1;


setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVHOPOPTS, &on, sizeof(on));

setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVDSTOPTS, &on, sizeof(on));

На рис. 27.6 показан формат объектов вспомогательных данных, используемый для отправки и получения параметров транзитных узлов и параметров получателя.

Рис. 27.6. Объекты вспомогательных данных, используемые для параметров транзитных узлов и параметров получателя

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

#include 


int inet6_opt_init(void *extbuf, socklen_t extlen);

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


int inet6_opt_append(void *extbuf, socklen_t extlen,

 int offset, uint8_t type, socklen_t len, uint_t align, void **databufp);

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


int inet6_opt_finish(void *extbuf, socklen_t extlen, int offset);

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


int inet6_opt_set_val(void *databuf, int offset,

 const void *val, socklen_t vallen);

Возвращает: новое смещение в буфере databuf

Функция

inet6_opt_init
возвращает количество байтов, необходимое для данного параметра. Если аргумент
extbuf
не является нулевым указателем, функция инициализирует заголовок расширения. Значение -1 возвращается при аварийном завершении работы в том случае, если аргумент
extlen
не кратен 8. (Все заголовки параметров транзитных узлов и получателя в IPv6 должны быть кратны 8.)

Функция

inet6_opt_append
возвращает общую длину заголовка расширения после добавления указанного при вызове параметра. Если аргумент
extbuf
не является нулевым указателем, функция дополнительно выполняет инициализацию параметра и вставляет необходимое заполнение. Значение -1 возвращается в случае аварийного завершения работы, если параметр не помещается в выделенный буфер. Аргумент
offset
представляет собой текущую полную длину, то есть значение, возвращенное при предыдущем вызове
inet6_opt_append
или
inet6_opt_init
. Аргументы
type
и
len
задают тип и длину параметра, они копируются непосредственно в его заголовок. Аргумент
align
указывает условие выравнивания, то есть значение x из выражения xn + y. Значение у вычисляется по
align
и
len
, поэтому указывать его явным образом необходимости нет. Аргумент
databufp
представляет собой адрес будущего указателя на значение параметра. Значение параметра копируется вызывающим процессом при помощи функции
inet6_opt_set_val
или любым другим методом.

Для завершения расширяющего заголовка вызывается функция

inet6_opt_finish
, которая добавляет в заголовок заполнение, делая его длину кратной 8 байтам. Как и раньше, заполнение добавляется в буфер только в том случае, если аргумент
extbuf
представляет собой непустой указатель. В противном случае функция вычисляет обновленное значение длины. Подобно
inet6_opt_append
, аргумент
offset
задает текущую полную длину (значение, возвращаемое
inet6_opt_append
и
inet6_opt_init
). Функция
inet6_opt_finish
возвращает полную длину возвращаемого заголовка или -1, если требуемое заполнение не помещается в предоставленный буфер.

Функция

inet6_opt_set_val
копирует значение параметра в буфер данных, возвращаемый
inet6_opt_append
. Аргумент
databuf
представляет собой указатель, возвращаемый
inet6_opt_append
. Аргумент
offset
представляет собой текущую длину внутри параметра, его необходимо инициализировать нулем для каждого параметра, а затем использовать возвращаемые
inet6_opt_set_val
значения по мере построения параметра. Аргументы
val
и
vallen
определяют значение для копирования в буфер значения параметра.

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

inet6_opt_init
,
inet6_opt_append
(один раз для каждого параметра) и
inet6_opt_finish
, передавая нулевой указатель и 0 в качестве аргументов
extbuf
и
extlen
соответственно. Затем можно динамически выделить буфер, использовав в качестве размера значение, возвращенное
inet6_opt_finish
. Этот буфер будет передаваться в качестве аргумента
extbuf
при втором проходе. Во время второго прохода вызываются функции
inet6_opt_init
и
inet6_opt_append
. Копирование значений параметров может выполняться как «вручную», так и при помощи функции
inet6_opt_set_val
. Наконец, мы должны вызвать
inet6_opt_finish
. Альтернативный вариант действий состоит в выделении буфера достаточно большого размера для нашего параметра. В этом случае первый проход можно не выполнять. Однако если изменение параметров приведет к переполнению выделенного буфера, в программе возникнет ошибка.

Оставшиеся три функции обрабатывают полученный параметр.

#include 


int inet6_opt_next(const void *extbuf, socklen_t extlen,

int offset, uint8_t *typep, socklen_t *lenp, void **databufp);

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


int inet6_opt_find(const void *extbuf, socklen_t extlen,

int offset, uint8_t type, socklen_t *lenp, void **databufp);

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


int inet6_opt_get_val(const void *databuf, int offset, void *val, socklen_t vallen);

Возвращает: новое значение смещения внутри буфера databuf

Функция

inet6_opt_next
обрабатывает следующий параметр в буфере. Аргументы
extbuf
и
extlen
определяют буфер, в котором содержится заголовок. Как и у
inet6_opt_append
, аргумент
offset
представляет собой текущее смещение внутри буфера. При первом вызове
inet6_opt_next
значение этого аргумента должно быть равно нулю, а при всех последующих — значению, возвращенному при предыдущем вызове функции. Аргументы
typep
,
lenp
и
databufp
предназначены для возвращения функцией типа, длины и значения параметра соответственно. Функция
inet6_opt_next
возвращает -1 в случае обработки заголовка с нарушенной структурой или в случае достижения конца буфера.

Функция

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

Функция

inet6_opt_get_val
предназначена для извлечения значений из параметра по указателю
databuf
, возвращаемому предшествующим вызовом
inet6_opt_next
или
inet6_opt_find
. Как и для
inet6_opt_set_val
, аргумент
offset
должен начинаться с 0 для каждого параметра, а затем должен приравниваться значению, возвращаемому предшествующим вызовом
inet6_opt_get_val
.

27.6. Заголовок маршрутизации IPv6

Заголовок маршрутизации IPv6 используется для маршрутизации от отправителя в IPv6. Первые два байта заголовка маршрутизации такие же, как показанные на рис. 27.3: поле следующего заголовка (next header) и поле длины заголовка расширения (header extension length). Следующие два байта задают тип маршрутизации (routing type) и количество оставшихся сегментов (number of segments left) (то есть сколько из перечисленных узлов еще нужно пройти). Определен только один тип заголовка маршрутизации, обозначаемый как тип 0. Формат заголовка маршрутизации показан на рис. 27.7.

Рис. 27.7. Заголовок маршрутизации IPv6

В заголовке маршрутизации IPv6 может появиться неограниченное количество адресов (реальное ограничение накладывается длиной пакета), а количество оставшихся сегментов не должно превышать количество адресов в заголовке. Документ RFC 2460 [27] описывает подробности обработки этого заголовка при пересылке его в направлении получателя. Там же вы можете найти подробно рассмотренный пример.

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

sendmsg
и возвращается в виде вспомогательных данных функцией
recvmsg
. Для отправки заголовка приложению не требуется выполнять какие-либо специальные действия — достаточно просто указать его при вызове функции
sendmsg
. Но для получения заголовка маршрутизации требуется, чтобы был включен параметр
IPV6_RECVRTHDR
:

const int on = 1;


setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVRTHDR, &on, sizeof(on));

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

#include 


socklen_t inet6_rth_space(int type, int segments);

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


void *inet6_rth_init(void *rthbuf, socklen_t rthlen, int type, int segments);

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


int inet6_rth_add(void *rthbuf, const struct in6_addr *addr);

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

Рис. 27.8. Объект вспомогательных данных для заголовка маршрутизации IPv6

Функция

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

Функция

inet6_rth_init
инициализирует буфер, на который указывает аргумент
rthbuf
, для помещения заголовка маршрутизации типа type и заданного количества сегментов. Возвращаемое значение этой функции — указатель на буфер. Этот указатель используется как аргумент при вызове следующей функции. Функция
inet6_rth_init
возвращает
NULL
в случае возникновения ошибок (например, при недостаточном размере предоставленного буфера).

Функция

inet6_rth_add
добавляет адрес IPv6, на который указывает аргумент
addr
, к концу составляемого заголовка маршрутизации. В случае успешного выполнения обновляется значение элемента
segleft
заголовка маршрутизации, чтобы учесть добавленный новый адрес.

Следующие три функции манипулируют полученным заголовком маршрутизации:

#include 


int inet6_rth_reverse(const void *in, void *out);

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


int inet6_rth_segments(const void *rthbuf);

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


struct in6_addr *inet6_rth_getaddr(const void *rthbuf, int index);

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

Функция

inet6_rth_reverse
принимает в качестве аргумента заголовок маршрутизации, полученный в виде объекта вспомогательных данных (на который указывает аргумент
in
), и создает новый заголовок маршрутизации (в буфере, на который указывает аргумент
out
), отправляющий дейтаграммы по обратному маршруту. Указатели in и out могут указывать на один и тот же буфер.

Функция

inet6_rth_segments
возвращает количество сегментов в заголовке маршрутизации, на который указывает
rthbuf
. В случае успешного выполнения функции возвращаемое значение оказывается больше 0.

Функция

inet6_rth_getaddr
возвращает указатель на адрес IPv6, заданный через
index
в заголовке маршрутизации
rthbuf
. Аргумент
index
должен лежать в пределах от 1 до значения, возвращенного функцией
inet6_rth_segments
, включительно.

Чтобы продемонстрировать использование этих параметров, мы создали UDP-клиент и UDP-сервер. Клиент представлен в листинге 27.5. Он принимает маршрут от отправителя в командной строке подобно TCP-клиенту IPv4, представленному в листинге 27.4. Сервер печатает маршрут полученного сообщения и обращает этот маршрут для отправки сообщения в обратном направлении.

Листинг 27.5. UDP-клиент, использующий маршрутизацию от отправителя

 1 #include "unp.h"


 2 int

 3 main(int argc, char **argv)

 4 {

 5  int с, sockfd, len = 0;

 6  u_char *ptr = NULL;

 7  void *rth;

 8  struct addrinfo *ai;


 9  if (argc < 2)

10   err_quit("usage: udpcli01 [  ... ] ");


11  if (argc > 2) {

12   int i;


13   len = Inet6_rth_space(IPV6_RTHDR_TYPE_0, argc-2);

14   ptr = Malloc(len);

15   Inet6_rth_init(ptr, len, IPV6_RTHDR_TYPE_0, argc-2);

16   for (i = 1; i < argc-1; i++) {

17    ai = Host_serv(argv[i], NULL, AF_INET6, 0);

18    Inet6_rth_add(ptr,

19     &((struct sockaddr_in6*)ai->ai_addr)->sin6_addr);

20   }

21  }


22  ai = Host_serv(argv[argc-1], SERV_PORT_STR, AF_INET6, SOCK_DGRAM);


23  sockfd = Socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);


24  if (ptr) {

25   Setsockopt(sockfd, IPPROTO_IPV6, IPV6_RTHDR, ptr, len);

26   free(ptr);

27  }


28  dg_cli(stdin, sockfd, ai->ai_addr, ai->ai_addrlen); /* do it all */


29  exit(0);

30 }

Создание маршрута

11-21
 Если при вызове программы было указано более одного аргумента, все параметры командной строки, за исключением последнего, формируют маршрут от отправителя. Сначала мы определяем, какой объем памяти займет заголовок маршрутизации, при помощи функции
inet6_rth_space
, затем выделяем буфер соответствующего размера вызовом
malloc
. После этого каждый адрес маршрута преобразуется в числовую форму функцией
host_serv
и добавляется к маршруту функцией
inet6_rth_add
. Примерно то же самое выполнял и TCP-клиент IPv4, за тем исключением, что здесь мы используем библиотечные функции, а не свои собственные.

Поиск адресата и создание сокета

22-23
 Мы определяем адрес назначения при помощи
host_serv
и создаем сокет для отправки пакетов.

Установка «закрепленного» параметра IPV6_RTHDR и вызов рабочей функции

24-27
 В разделе 27.7 будет показано, что не обязательно отправлять одни и те же вспомогательные данные с каждым пакетом. Вместо этого можно вызвать
setsockopt
таким образом, что один и тот же заголовок будет добавляться ко всем пакетам в рамках одного сеанса. Этот параметр устанавливается только в том случае, если указатель
ptr
не нулевой, то есть мы уже должны были выделить буфер под заголовок маршрутизации. На последнем этапе мы вызываем рабочую функцию
dg_cli
, которая не меняется с листинга 8.4.

Программа UDP-сервера не изменилась по сравнению с предыдущими примерами. Сервер открывает сокет и вызывает функцию

dg_echo
. В листинге 27.6 представлена функция
dg_echo
, печатающая информацию о маршруте от источника (если таковой был получен) и обращающая этот маршрут для отправки сообщения в обратном направлении.

Листинг 27.6. Функция dg_echo, печатающая маршрут

//ipopts/dgechoprintroute.c

 1 #include "unp.h"


 2 void

 3 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)

 4 {

 5  int n;

 6  char mesg[MAXLINE];

 7  int on;

 8  char control[MAXLINE];

 9  struct msghdr msg;

10  struct cmsghdr *cmsg;

11  struct iovec iov[1];


12  on = 1;

13  Setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVRTHDR, &on, sizeof(on));


14  bzero(&msg, sizeof(msg));

15  iov[0].iov_base = mesg;

16  msg.msg_name = pcliaddr;

17  msg.msg_iov = iov;

18  msg.msg_iovlen = 1;

19  msg.msg_control = control;

20  for (;;) {

21   msg.msg_namelen = clilen;

22   msg.msg_controllen = sizeof(control);

23   iov[0].iov_len = MAXLINE;

24   n = Recvmsg(sockfd, &msg, 0);


25   for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;

26    cmsg = CMSG_NXTHDR(&msg, cmsg)) {

27    if (cmsg->cmsg_level == IPPROTO_IPV6 &&

28     cmsg->cmsg_type == IPV6_RTHDR) {

29     inet6_srcrt_print(CMSG_DATA(cmsg));

30     Inet6_rth_reverse(CMSG_DATA(cmsg), CMSG_DATA(cmsg));

31    }

32   }


33   iov[0].iov_len = n;

34   Sendmsg(sockfd, &msg, 0);

35  }

36 }

Включение параметра IPV6_RECVRTHDR и подготовка структуры msghdr

12-13
 Чтобы получить информацию о маршруте, мы должны установить параметр сокета
IPV6_RECVRTHDR
. Кроме того, мы должны использовать функцию
recvmsg
, поэтому мы настраиваем поля структуры
msghdr
, которые не требуют изменения.

Настройка изменяемых полей и вызов recvmsg

21-24
 Мы устанавливаем размер полей длины и вызываем
recvmsg
.

Поиск и обработка маршрута от отправителя

25-32
 Мы перебираем вспомогательные данные, используя
CMSG_FIRSTHDR
и
CMSG_NXTHDR
. Несмотря на то, что мы ожидаем получить только один объект вспомогательных данных, выполнить такой перебор всегда полезно. Если мы обнаруживаем заголовок маршрутизации, он распечатывается функцией
inet6_srcrt_print
(листинг 27.7). Затем маршрут обращается функцией
inet6_rth_reverse
для последующего использования при возвращении пакета клиенту. В данном случае обращение производится без копирования в новый буфер, так что можно использовать старый объект вспомогательных данных для отправки пакета клиенту.

Отправка эхо-пакета

33-34
 Мы устанавливаем длину пакета и передаем его клиенту вызовом
sendmsg
.

Благодаря наличию вспомогательных библиотечных функций IPv6 наша функция

inet6_srcrt_print
становится почти тривиальной.

Листинг 27.7. Функция inet6_srcrt_print: вывод маршрута

 1 #include "unp.h"


 2 void

 3 inet6_srcrt_print(void *ptr)

 4 {

 5  int i, segments;

 6  char str[INET6_ADDRSTRLEN];


 7  segments = Inet6_rth_segments(ptr);

 8  printf("received source route: ");

 9  for (i = 0; i < segments; i++)

10   printf("%s ", Inet_ntop(AF_INET6, Inet6_rth_getaddr(ptr, i),

11    str, sizeof(str)));

12  printf("\n");

13 }

Определение количества сегментов маршрута

7
 Количество сегментов маршрута определяется функцией
inet6_rth_segments
.

Перебор сегментов

9-11
 Мы перебираем сегменты маршрута, вызывая для каждого из них
inet6_rth_getaddr
и преобразуя адреса в формат представления функцией
inet_ntop
.

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

27.7. «Закрепленные» параметры IPv6

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

sendmsg
и
recvmsg
для отправки и получения следующих семи различных типов объектов вспомогательных данных:

1. Информация о пакете IPv6: структура

in6_pktinfo
, содержащая адрес получателя и индекс интерфейса для исходящих дейтаграмм либо адрес отправителя и индекс интерфейса для приходящих дейтаграмм (индекс принимающего интерфейса) (см. рис. 22.5).

2. Предельное количество транзитных узлов для исходящих или приходящих дейтаграмм (см. рис. 22.5).

3. Адрес следующего транзитного узла (см. рис. 22.5).

4. Класс исходящего или входящего трафика (см. рис. 22.5).

5. Параметры транзитных узлов (см. рис. 27.6).

6. Параметры получателя (см. рис. 27.6).

7. Заголовок маршрутизации (см. рис. 27.8).

В табл. 14.4 приведены значения полей

cmsg_level
и
cmsg_type
для этих объектов, а также значения для других объектов вспомогательных данных.

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

sendmsg
, мы можем установить соответствующие параметры сокета. Параметры сокета используют те же константы, что и вспомогательные данные, то есть уровень параметра всегда должен иметь значение
IPPROTO_IPV6
, а название параметра может быть
IPV6_DSTOPTS
,
IPV6_HOPLIMIT
,
IPV6_HOPOPTS
,
IPV6_NEXTHOP
,
IPV6_PKTINFO
,
IPV6_RTHDR
или
IPV6_TCLASS
. Закрепленные параметры могут быть заменены для конкретного пакета в случае сокета UDP или символьного сокета IPv6, если при вызове функции
sendmsg
задать какие-либо другие параметры в качестве объектов вспомогательных данных. Если при вызове функции
sendmsg
указаны какие-либо вспомогательные данные, ни один из закрепленных параметров не будет послан с этим пакетом.

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

sendmsg
или
recvmsg
на сокете TCP. Вместо этого приложение TCP может установить соответствующий параметр сокета и указать любой из упомянутых в начале этого раздела семи объектов вспомогательных данных. Тогда эти параметры будут относиться ко всем пакетам, отсылаемым с данного сокета. Поведение при повторной передаче пакетов, первоначально переданных до изменения закрепленных параметров, не определено: могут использоваться как старые, так и новые значения параметров.

Не существует способа получить параметры, принятые в IP-пакете по TCP, потому что в этом протоколе отсутствует соответствие между пакетами и операциями чтения из сокета, выполняемыми пользователем.

27.8. История развития интерфейса IPv6

Документ RFC 2292 [113] определял более раннюю версию описываемого интерфейса, которая была реализована в некоторых системах. В этой версии для работы с параметрами получателя и транзитных узлов использовались функции

inet6_option_space
,
inet6_option_init
,
inet6_option_append
,
inet6_option_alloc
,
inet6_option_next
и
inet6_option_find
. Эти функции работали непосредственно с объектами типа
struct cmsghdr
, предполагая, что все параметры содержатся во вспомогательных данных. Для работы с заголовками маршрутизации были предназначены функции
inet6_rthdr_space
,
inet6_rthdr_init
,
inet6_rthdr_add
,
inet6_rthdr_lasthop
,
inet6_rthdr_reverse
,
inet6_rthdr_segments
,
inet6_rthdr_getaddr
и
inet6_rthdr_getflags
. Эти функции также работали непосредственно со вспомогательными данными.

В этом API закрепленные параметры устанавливались при помощи параметра сокета

IPV6_PKTOPTIONS
. Объекты вспомогательных данных при этом передавались в качестве данных параметра
IPV6_PKTOPTIONS
. Нынешние параметры сокета
IPV6_DSTOPTS
,
IPV6_HOPOPTS
и
IPV6_RTHDR
были флагами, позволявшими получать соответствующие заголовки во вспомогательных данных.

Подробнее обо всем этом вы можете прочесть в разделах 4–8 документа RFC 2292 [113].

27.9. Резюме

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

IP_OPTIONS
.

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

sendmsg
и возвращаются функцией
recvmsg
также в виде вспомогательных данных.

Упражнения

1. Что изменится, если в нашем примере, приведенном в конце раздела 27.3, мы зададим каждый промежуточный узел с параметром

-G
вместо
-g
?

2. Размер буфера, указываемый в качестве аргумента функции

setsockopt
для параметра сокета
IP_OPTIONS
, должен быть кратен 4 байтам. Что бы нам пришлось делать, если бы мы не поместили параметр NOP в начало буфера, как показано на рис. 27.1?

3. Каким образом программа

ping
получает маршрут от отправителя, когда используется параметр IP Record Route (запись маршрута), описанный в разделе 7.3 [128]?

4. Почему в примере кода для сервера

rlogind
, приведенном в конце раздела 27.3, который предназначен для удаления полученного маршрута от отправителя, дескриптор сокета (первый аргумент функций
getsockopt
и
setsockopt
) имеет нулевое значение?

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

optsize = 0;

setsockopt(0, ipproto, IP_OPTIONS, NULL, &optsize);

Что в этом фрагменте неправильно? Имеет ли это значение?

Глава 28