Книга: UNIX: разработка сетевых приложений
Демон icmpd
Разделы на этой странице:
- Массив client
- Инициализация массива client
- Создание сокетов
- Проверка прослушиваемого доменного сокета Unix
- Проверка символьных сокетов ICMP
- Проверка присоединенных доменных сокетов Unix
- Считывание данных клиента и, возможно, дескриптора
- ПРИМЕЧАНИЕ
- Получение номера порта, связанного с сокетом UDP
- Сообщение клиенту об успехе
- Закрытие UDP-сокета клиента
- Обработка ошибок и завершение работы клиента
- Проверка типа сообщения, уведомление приложения
- Проверка ошибки UDP, поиск клиента
- Построение структуры icmpd_err
Демон 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-25client
. Код данной функции скопирован из начала кода, приведенного в листинге 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 }
- Эхо-клиент UDP, использующий демон icmpd
- 28.7. Демон сообщений ICMP
- 5.14 МОНТИРОВАНИЕ И ДЕМОНТИРОВАНИЕ ФАЙЛОВЫХ СИСТЕМ
- Демонтаж и установка МП
- Демонтаж
- 5.4. Демон inetd
- Почему мы настаиваем на том, чтобы каждый спринт заканчивался демонстрацией
- 7.2.6.3. Системные демоны и традиционные сигналы
- Пример приложения Pocket PC, демонстрирующий работу средств контроля запуска событий
- Демонстрация приемов работы на конкретном примере
- 2. Плата за демонстрацию и рекламу товара
- Листинг 13.6. Код формы, демонстрирующий загрузку встроенных ресурсов