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

Демон icmpd

Демон icmpd

Начинаем описание нашего демона icmpd с заголовочного файла icmpd.h, приведенного в листинге 28.23.

Листинг 28.23. Заголовочный файл icmpd.h для демона icmpd

//icmpd/icmpd.h
 1 #include "unpicmpd.h"
 2 struct client {
 3  int connfd; /* потоковый доменный сокет Unix к клиенту */
 4  int family; /* AF_INET или AF_INET6 */
 5  int lport;  /* локальный порт, связанный с UDP-сокетом клиента */
 6              /* сетевой порядок байтов */
 7 } client[FD_SETSIZE];
 8 /* глобальные переменные */
 9 int fd4, fd6, listenfd, maxi, maxfd, nready;
10 fd_set rset, allset;
11 struct sockaddr_un cliaddr;
12 /* прототипы функций */
13 int readable_conn(int);
14 int readable_listen(void);
15 int readable_v4(void);
16 int readable_v6(void);

Массив client

2-17 Поскольку демон может обрабатывать любое количество клиентов, для сохранения информации о каждом клиенте используется массив структур client. Они аналогичны структурам данных, которые использовались в разделе 6.8. Кроме дескриптора для доменного сокета Unix, через который осуществляется связь с клиентом, сохраняется также семейство адресов клиентского UDP-сокета AF_INET или AF_INET6 и номер порта, связанного с сокетом. Далее объявляются прототипы функций и глобальные переменные, совместно используемые этими функциями.

В листинге 28.24 приведена первая часть функции main.

Листинг 28.24. Первая часть функции main: создание сокетов

//icmpd/icmpd.c
 1 #include "icmpd.h"
 2 int
 3 main(int argc, char **argv)
 4 {
 5  int i, sockfd;
 6  struct sockaddr_un sun;
 7  if (argc != 1)
 8   err_quit("usage: icmpd");
 9  maxi = -1; /* индекс массива client[] */
10  for (i = 0; i < FD_SETSIZE; i++)
11   client[i].connfd = -1; /* -1 означает свободный элемент */
12  FD_ZERO(&allset);
13  fd4 = Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
14  FD_SET(fd4, &allset);
15  maxfd = fd4;
16 #ifdef IPV6
17  fd6 = Socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
18  FD_SET(fd6, &allset);
19  maxfd = max(maxfd, fd6);
20 #endif
21  listenfd = Socket(AF_UNIX, SOCK_STREAM, 0);
22  sun.sun_family = AF_LOCAL;
23  strcpy(sun.sun_path, ICMPD_PATH);
24  unlink(ICMPD_PATH);
25  Bind(listenfd, (SA*)&sun, sizeof(sun));
26  Listen(listenfd, LISTENQ);
27  FD_SET(listenfd, &allset);
28  maxfd = max(maxfd, listenfd);

Инициализация массива client

9-10 Инициализируется массив client путем присваивания значения -1 элементу присоединенного сокета.

Создание сокетов

12-28 Создаются три сокета: символьный сокет ICMPv4, символьный сокет ICMPv6 и потоковый доменный сокет Unix. Мы связываем при помощи функции bind свое заранее известное полное имя с сокетом и вызываем функцию listen. Это сокет, к которому клиенты присоединяются с помощью функции connect. Для функции select также вычисляется максимальный дескриптор, а для вызовов функции accept в памяти размещается структура адреса сокета.

В листинге 28.25 приведена вторая часть функции main. Она содержит бесконечный цикл, вызывающий функцию select в ожидании, когда будет готов к чтению какой-либо из дескрипторов демона.

Листинг 28.25. Вторая часть функции main: обработка готового к чтению дескриптора

//icmpd/icmpd.c
29  for (;;) {
30   rset = allset;
31   nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
32   if (FD_ISSET(listenfd, &rset))
33    if (readable_listen() <= 0)
34     continue;
35   if (FD_ISSET(fd4, &rset))
36    if (readable_v4() <= 0)
37     continue;
38 #ifdef IPV6
39   if (FD_ISSET(fd6, &rset))
40    if (readable_v6() <= 0)
41     continue;
42 #endif
43   for (i = 0; i <= maxi; i++) { /* проверка всех клиентов */
44    if ( (sockfd = client[i].connfd) < 0)
45     continue;
46    if (FD_ISSET(sockfd, &rset))
47     if (readable_conn(i) <= 0)
48      break; /* готовых дескрипторов больше нет */
49   }
50  }
51  exit(0);
52 }

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

32-34 Прослушиваемый доменный сокет Unix проверяется в первую очередь, и если он готов, запускается функция readable_listen. Переменная nready — количество дескрипторов, которое функция select возвращает как готовые к чтению — является глобальной. Каждая из наших функций readablе_XXX уменьшает ее значение на 1, и новое значение этой переменной является возвращаемым значением функции. Когда ее значение достигает нуля, это говорит о том, что все готовые к чтению дескрипторы обработаны, и поэтому функция select вызывается снова.

Проверка символьных сокетов ICMP

35-42 Проверяется символьный сокет ICMPv4, а затем символьный сокет ICMPv6.

Проверка присоединенных доменных сокетов Unix

43-49 Затем проверяется, готов ли для чтения какой-нибудь из присоединенных доменных сокетов Unix. Готовность для чтения какого-либо из таких сокетов обозначает, что клиент отослал дескриптор или завершился.

В листинге 28.26 приведена функция readable_listen, вызываемая, когда прослушиваемый сокет готов для чтения. Это указывает на новое клиентское соединение.

Листинг 28.26. Обработка нового соединения клиента

//icmpd/readablе_listen.c
 1 #include "icmpd.h"
 2 int
 3 readable_listen(void)
 4 {
 5  int i, connfd;
 6  socklen_t clilen;
 7  clilen = sizeof(cliaddr);
 8  connfd = Accept(listenfd, (SA*)&cliaddr, &clilen);
 9  /* поиск первой свободной структуры в массиве client[] */
10  for (i = 0; i < FD_SETSIZE; i++)
11   if (client[i].connfd < 0) {
12    client[i].connfd = connfd; /* сохранение дескриптора */
13    break;
14   }
15  if (i == FD_SETSIZE) {
16   close(connfd); /* невозможно обработать новый клиент */
17   return(--nready); /* грубое закрытие нового соединения */
18  }
19  printf("new connection, i = %d, connfd = %dn", i, connfd);
20  FD_SET(connfd, &allset); /* добавление нового дескриптора в набор */
21  if (connfd > maxfd)
22   maxfd = connfd; /* для select() */
23  if (i > maxi)
24   maxi = i; /* максимальный индекс в массиве client[] */
25  return(--nready);
26 }
7-25
 Принимается соединение и используется первый свободный элемент массива client. Код данной функции скопирован из начала кода, приведенного в листинге 6.4. Если свободных элементов в массиве нет, мы закрываем новое соединение и занимаемся обслуживанием уже имеющихся клиентов.

Когда присоединенный сокет готов для чтения, вызывается функция readablе_conn (листинг 28.27), а ее аргументом является индекс данного клиента в массиве client.

Листинг 28.27. Считывание данных и, возможно, дескриптора от клиента

//icmpd/readable_conn.c
 1 #include "icmpd.h"
 2 int
 3 readable_conn(int I)
 4 {
 5  int unixfd, recvfd;
 6  char c;
 7  ssize_t n;
 8  socklen_t len;
 9  struct sockaddr_storage ss;
10  unixfd = client[i].connfd;
11  recvfd = -1;
12  if ((n = Read_fd(unixfd, &c, 1, &recvfd)) == 0) {
13   err_msg("client %d terminated, recvfd = %d", i, recvfd);
14   goto clientdone; /* вероятно, клиент завершил работу */
15  }
16  /* данные от клиента, должно быть, дескриптор */
17  if (recvfd < 0) {
18   err_msg("read_fd did not return descriptor");
19   goto clienterr;
20  }

Считывание данных клиента и, возможно, дескриптора

13-18 Вызываем функцию read_fd, приведенную в листинге 15.9, для считывания данных и, возможно, дескриптора. Если возвращаемое значение равно нулю, клиент закрыл свою часть соединения, вероятно, завершив свое выполнение.

ПРИМЕЧАНИЕ

При написании кода пришлось выбирать, что использовать для связи между приложением и демоном — либо потоковый доменный сокет Unix, либо дейтаграммный доменный сокет Unix. Дескриптор сокета UDP может быть передан через любой доменный сокет Unix. Причина, по которой предпочтение было отдано потоковому сокету, заключается в том, что он позволяет определить момент завершения клиента. Все дескрипторы автоматически закрываются, когда клиент завершает работу, в том числе и доменный сокет Unix, используемый для связи с демоном, в результате чего данный клиент удаляется демоном из массива client. Если бы мы использовали сокет дейтаграмм, то не узнали бы, когда клиент завершил работу.

16-20 Если клиент не закрыл соединение, ждем получения дескриптора. Вторая часть функции readable_conn приведена в листинге 28.28.

Листинг 28.28. Получение номера порта, который клиент связал с UDP-сокетом

//icmpd/readable_conn.c
21  len = sizeof(ss);
22  if (getsockname(recvfd, (SA*)&ss, &len) < 0) {
23   err_ret("getsockname error");
24   goto clienterr;
25  }
26  client[i].family = ss.ss_family;
27  if ((client[i].lport = sock_get_port((SA*)&ss, len)) == 0) {
28   client[i].lport = sock_bind_wild(recvfd, client[i].family);
29   if (client[i].lport <= 0) {
30    err_ret("error binding ephemeral port");
31    goto clienterr;
32   }
33  }
34  Write(unixfd, "1", 1); /* сообщение клиенту об успехе */
35  Close(recvfd); /* работа с UDP-сокетом клиента завершена */
36  return(--nready);
37 clienterr:
38  Write(unixfd, "0", 1); /* сообщение клиенту об ошибке */
39 clientdone:
40  Close(unixfd);
41  if (recvfd >= 0)
42   Close(recvfd);
43  FD_CLR(unixfd, &allset);
44  client[i].connfd = -1;
45  return(--nready);
46 }

Получение номера порта, связанного с сокетом UDP

21-25 Вызывается функция getsockname, так что демон может получить номер порта, связанного с сокетом. Поскольку неизвестно, каков размер буфера, необходимого для размещения структуры адреса сокета, мы используем структуру sockaddr_storage, которая достаточно велика для структуры адреса сокета любого поддерживаемого системой типа и обеспечивает нужное выравнивание.

26-33 Семейство адресов сокета вместе с номером порта сохраняется в структуре client. Если номер порта равен нулю, мы вызываем функцию sock_bind_wild для связывания универсального адреса и динамически назначаемого порта с сокетом, но, как отмечалось ранее, такой подход не работает в реализациях SVR4.

Сообщение клиенту об успехе

34 Один байт, содержащий символ "1", отправляется обратно клиенту.

Закрытие UDP-сокета клиента

35 Заканчиваем работу с UDP-сокетом клиента и закрываем его с помощью функции close. Дескриптор был переслан нам клиентом и, таким образом, является копией; следовательно, UDP-сокет все еще открыт на стороне клиента.

Обработка ошибок и завершение работы клиента

37-45 Если происходит ошибка, клиент получает нулевой байт. Когда клиент завершается, наша часть доменного сокета Unix закрывается, и соответствующий дескриптор удаляется из набора дескрипторов для функции select. Полю connfd структуры client присваивается значение -1, что является указанием на ее освобождение.

Функция readable_v4 вызывается, когда символьный сокет ICMPv4 открыт для чтения. Первая часть данной функции приведена в листинге 28.29. Этот код аналогичен коду для ICMPv4, приведенному ранее в листингах 28.6 и 28.15.

Листинг 28.29. Обработка полученных дейтаграмм ICMPv4, первая часть

//icmpd/readable_v4.c
 1 #include "icmpd.h"
 2 #include <netinet/in_systm.h>
 3 #include <netinet/ip.h>
 4 #include <netinet/ip_icmp.h>
 5 #include <netinet/udp.h>
 6 int
 7 readable_v4(void)
 8 {
 9  int i, hlen1, hlen2, icmplen, sport;
10  char buf[MAXLINE];
11  char srcstr[INET_ADDRSTRLEN], dststr[INET_ADDRSTRLEN];
12  ssize_t n;
13  socklen_t len;
14  struct ip *ip, *hip;
15  struct icmp *icmp;
16  struct udphdr *udp;
17  struct sockaddr_in from, dest;
18  struct icmpd_err icmpd_err;
19  len = sizeof(from);
20  n = Recvfrom(fd4, buf, MAXLINE, 0, (SA*)&from, &len);
21  printf("%d bytes ICMPv4 from %s:", n, Sock_ntop_host((SA*)&from, len));
22  ip = (struct ip*)buf; /* начало IP-заголовка */
23  hlen1 = ip->ip_hl << 2; /* длина IP-заголовка */
24  icmp = (struct icmp*)(buf + hlen1); /* начало ICMP-заголовка */
25  if ((icmplen = n - hlen1) < 8)
26   err_quit("icmplen (%d) < 8", icmplen);
27  printf(" type = %d, code = %dn", icmp->icmp_type, icmp->icmp_code);

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

В листинге 28.30 приведена вторая часть функции readable_v4.

Листинг 28.30. Обработка полученных дейтаграмм ICMPv4, вторая часть

//icmpd/readable_v4.c
28  if (icmp->icmp_type == ICMP_UNREACH ||
29   icmp->icmp_type ==ICMP_TIMXCEED ||
30   icmp->icmp_type == ICMP_SOURCEQUENCH) {
31   if (icmplen < 8 + 20 + 8)
32    err_quit("icmplen (%d) < 8 + 20 + 8, icmplen);
33   hip = (struct ip*)(buf + hlen1 + 8);
34   hlen2 = hip->ip_hl << 2;
35   printf("tsrcip = %s, dstip = %s, proto = %dn",
36    Inet_ntop(AF_INET, &hip->ip_src, srcstr, sizeof(srcstr)),
37    Inet_ntop(AF_INET, &hip->ip_dst, dststr, sizeof(dststr)),
38    hip->ip_p);
39   if (hip->ip_p == IPPROTO_UDP) {
40    udp = (struct udphdr*)(buf + hlen1 + 8 + hlen2);
41    sport = udp->uh_sport;
42    /* поиск доменного сокета клиента, отправка заголовка */
43    for (i = 0; i <= maxi; i++) {
44     if (client[i].connfd >= 0 &&
45      client[i].family == AF_INET &&
46      client[i].lport == sport) {
47      bzero(&dest, sizeof(dest));
48      dest.sin_family = AF_INET;
49 #ifdef HAVE_SOCKADDR_SA_LEN
50      dest.sin_len = sizeof(dest);
51 #endif
52      memcpy(&dest.sin_addr, &hip->ip_dst,
53       sizeof(struct in_addr));
54      dest.sin_port = udp->uh_dport;
55      icmpd_err.icmpd_type = icmp->icmp_type;
56      icmpd_err.icmpd_code = icmp->icmp_code;
57      icmpd_err.icmpd_len = sizeof(struct sockaddr_in);
58      memcpy(&icmpd_err.icmpd_dest, &dest, sizeof(dest));
59      /* преобразование кода и типа ICMP в значение errno */
60      icmpd_err.icmpd_errno = EHOSTUNREACH; /* по умолчанию */
61      if (icmp->icmp_type == ICMP_UNREACH) {
62       if (icmp->icmp_code == ICMP_UNREACH_PORT)
63        icmpd_err.icmpd_errno = ECONNREFUSED;
64       else if (icmp->icmp_code == ICMP_UNREACH_NEEDFRAG)
65        icmpd_err.icmpd_errno = EMSGSIZE;
66      }
67      Write(client[i].connfd, &icmpd_err, sizeof(icmpd_err));
68     }
69    }
70   }
71  }
72  return(--nready);
73 }

Проверка типа сообщения, уведомление приложения

29-31 ICMP-сообщения, которые посылаются приложениям, — это сообщения о недоступности порта, превышении времени и завершении клиента (см. табл. 28.1).

Проверка ошибки UDP, поиск клиента

34-42 Указатель hip указывает на IP-заголовок, который возвращается сразу после заголовка ICMP. Это IP-заголовок дейтаграммы, вызвавшей ICMP-ошибку. Мы убеждаемся, что эта IP-дейтаграмма является UDP-дейтаграммой, а затем извлекаем номер UDP-порта из UDP-заголовка, следующего за IP-заголовком.

43-55 По всем структурам client осуществляется поиск подходящего семейства адресов и порта. Если соответствие найдено, строится структура адреса сокета IPv4, которая содержит IP-адрес получателя и порт из UDP-дейтаграммы, вызвавшей ошибку.

Построение структуры icmpd_err

56-70 Строится структура icmpd_err, посылаемая клиенту через доменный сокет Unix. Тип и код сообщения ICMP сначала отображаются в значение errno, как показано в табл. 28.1.

Ошибки ICMPv6 обрабатываются функцией readable_v6, первая часть которой приведена в листинге 28.31. Обработка ошибок ICMPv6 аналогична коду, приведенному в листингах 28.7 и 28.16.

Листинг 28.31. Обработка полученной дейтаграммы ICMPv6, первая часть

//icmpd/readable_v6.c
 1 #include "icmpd.h"
 2 #include <netinet/in_systm.h>
 3 #include <netinet/ip.h>
 4 #include <netinet/ip_icmp.h>
 5 #include <netinet/udp.h>
 6 #ifdef IPV6
 7 #include <netinet/ip6.h>
 8 #include <netinet/icmp6.h>
 9 #endif
10 int
11 readable_v6(void)
12 {
13 #ifdef IPV6
14  int i, hlen2, icmp6len, sport;
15  char buf[MAXLINE];
16  char srcstr[INET6_ADDRSTRLEN], dststr[INET6_ADDRSTRLEN];
17  ssize_t n;
18  socklen_t len;
19  struct ip6_hdr *ip6, *hip6;
20  struct icmp6_hdr *icmp6;
21  struct udphdr *udp;
22  struct sockaddr_in6 from, dest;
23  struct icmpd_err icmpd_err;
24  len = sizeof(from);
25  n = Recvfrom(fd6, buf, MAXLINE, 0, (SA*)&from, &len);
26  printf("%d bytes ICMPv6 from %s:", n, Sock_ntop_host((SA*)&from, len));
27  icmp6 = (struct icmp6_hdr*)buf; /* начало заголовка ICMPv6 */
28  if ((icmp6len = n) < 8)
29   err_quit("icmp6len (%d) < 8", icmp6len);
30  printf(" type = %d, code = %dn", icmp6->icmp6_type, icmp6->icmp6_code);

Вторая часть функции readable_v6 приведена в листинге 28.32. Код аналогичен приведенному в листинге 28.30: мы проверяем тип ICMP-ошибки, убеждаемся, что дейтаграмма, вызвавшая ошибку, является UDP-дейтаграммой, а затем строим структуру icmpd_err, которую отсылаем клиенту.

Листинг 28.32. Обработка полученной дейтаграммы ICMPv6, вторая часть

//icmpd/readable_v6.c
31  if (icmp6->icmp6_type == ICMP6_DST_UNREACH ||
32   icmp6->icmp6_type == ICMP6_PACKET_TOO_BIG ||
33   icmp6->icmp6_type == ICMP6_TIME_EXCEEDED) {
34   if (icmp6len < 8+8)
35    err_quit("icmp6len (%d) < 8 + 8", icmp6len);
36   hip6 = (struct ip6_hdr*)(buf + 8);
37   hlen2 = sizeof(struct ip6_hdr);
38   printf("tsrcip = %s, dstip = %s, next hdr = %dn",
39    Inet_ntop(AF_INET6, &hip6->ip6_src, srcstr, sizeof(srcstr)),
40    Inet_ntop(AF_INET6, &hip6->ip6_dst, dststr, sizeof(dststr)),
41    hip6->ip6_nxt);
42   if (hip6->ip6_nxt == IPPROTO_UDP) {
43    udp = (struct udphdr*)(buf + 8 + hlen2);
44    sport = udp->uh_sport;
45    /* поиск доменного сокета клиента, отправка заголовков */
46    for (i = 0; i <= maxi; i++) {
47     if (client[i].connfd >= 0 &&
48      client[i].family == AF_INET6 &&
49      client[i].lport == sport) {
50      bzero(&dest, sizeof(dest));
51      dest.sin6_family = AF_INET6;
52 #ifdef HAVE_SOCKADDR_SA_LEN
53      dest.sin6_len = sizeof(dest);
54 #endif
55      memcpy(&dest.sin6_addr, &hip6->ip6_dst,
56       sizeof(struct in6_addr));
57      dest.sin6_port = udp->uh_dport;
58      icmpd_err.icmpd_type = icmp6->icmp6_type;
59      icmpd_err.icmpd_code = icmp6->icmp6_code;
60      icmpd_err.icmpd_len = sizeof(struct sockaddr_in6);
61      memcpy(&icmpd_err.icmpd_dest, &dest, sizeof(dest));
62      /* преобразование типа и кода ICMPv6 к значению errno */
63      icmpd_err.icmpd_errno = EHOSTUNREACH; /* по умолчанию */
64      if (icmp6->icmp6_type == ICMP6_DST_UNREACH &&
65       icmp6->icmp6_code ICMP6_DST_UNREACH_NOPORT)
66       icmpd_err.icmpd_errno = ECONNREFUSED;
67      if (icmp6->icmp6_type == ICMP6_PACKET_TOO_BIG)
68       icmpd_err.icmpd_errno = EMSGSIZE;
69      Write(client[i].connfd, &icmpd_err, sizeof(icmpd_err));
70     }
71    }
72   }
73  }
74  return(--nready);
75 #endif
76 }

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


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