UNIX: разработка сетевых приложений - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
Со структурой icmp6_filter работают шесть макросов.
#include <netinet/icmp6.h>
void ICMP6_FILTER_SETPASSALL(struct icmp6_filter *filt);
void ICMP6_FILTER_SETBLOCKALL(struct icmp6_filter *filt);
void ICMP6_FILTER_SETPASS(int msgtype, struct icmp6_filter *filt);
void ICMP6_FILTER_SETBLOCK(int msgtype, struct icmp6_filter *filt);
int ICMP6_FILTER_WILLPASS(int msgtype, const struct icmp6_filter *filt);
int ICMP6_FILTER_WILLBLOCK(int msgtype, const struct icmp6_filter *filt);
Все возвращают: 1, если фильтр пропускает (блокирует) сообщение данного типа, 0 в противном случае
Аргумент filt всех макрокоманд является указателем на переменную icmp6_filter, изменяемую первыми четырьмя макрокомандами и проверяемую последними двумя. Аргумент msgtype является значением в интервале от 0 до 255, определяющим тип ICMP-сообщения.
Макрокоманда SETPASSALL указывает, что все типы сообщений должны пересылаться приложению, а макрокоманда SETBLOCKALL — что никакие сообщения не должны посылаться приложениям. По умолчанию при создании символьного сокета ICMPv6 подразумевается, что все типы ICMP-сообщений пересылаются приложению.
Макрокоманда SETPASS определяет конкретный тип сообщений, который должен пересылаться приложению, а макрокоманда SETBLOCK блокирует один конкретный тип сообщений. Макрокоманда WILLPASS возвращает значение 1, если определенный тип пропускается фильтром. Макрокоманда WILLBLOCK возвращает значение 1, если определенный тип блокирован фильтром, и нуль в противном случае.
В качестве примера рассмотрим приложение, которое будет получать только ICMPv6-извещения маршрутизатора:
struct icmp6_filter myfilt;
fd = Socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
ICMP6_FILTER_SETBLOCKALL(&myfilt);
ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &myfilt);
Setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &myfilt, sizeof(myfilt));
Сначала мы блокируем все типы сообщений (поскольку по умолчанию все типы сообщений пересылаются), а затем разрешаем пересылать только извещения маршрутизатора. Несмотря на то, что мы используем фильтр, приложение должно быть готово к получению всех типов пакетов ICMPv6, потому что любые пакеты ICMPv6, полученные между вызовами socket и setsockopt, будут добавлены в очередь на сокете. Параметр ICMP6_FILTER — лишь средство оптимизации условий функционирования приложения.
28.5. Программа ping
В данном разделе приводится версия программы ping, работающая как с IPv4, так и с IPv6. Вместо того чтобы представить известный доступный исходный код, мы разработали оригинальную программу, и сделано это по двум причинам. Во-первых, свободно доступная программа ping страдает общей болезнью программирования, известной как «ползучий улучшизм» (стремление к постоянным ненужным усложнениям программы в погоне за мелкими улучшениями): она поддерживает 12 различных параметров. Наша цель при исследовании программы ping в том, чтобы понять концепции и методы сетевого программирования и не быть при этом сбитыми с толку ее многочисленными параметрами. Наша версия программы ping поддерживает только один параметр и занимает в пять раз меньше места, чем общедоступная версия. Во-вторых, общедоступная версия работает только с IPv4, а нам хочется показать версию, поддерживающую также и IPv6.
Действие программы ping предельно просто: по некоторому IP-адресу посылается эхо-запрос ICMP, и этот узел отвечает эхо-ответом ICMP. Оба эти сообщения поддерживаются в обеих версиях — и в IPv4, и в IPv6. На рис. 28.1 приведен формат ICMP-сообщений.
Рис. 28.1. Формат сообщений эхо-запроса и эхо-ответа ICMPv4 и ICMPv6
В табл. А.5 и А.6 приведены значения поля тип (type) для этих сообщений и говорится, что значение поля код (code) равно нулю. Далее будет показано, что в поле идентификатор (identifier) указывается идентификатор процесса ping, а значение поля порядковый номер (sequence number) увеличивается на 1 для каждого отправляемого пакета. В поле дополнительные данные (optional data) сохраняется 8-байтовая отметка времени отправки пакета. Правила ICMP-запроса требуют, чтобы идентификатор, порядковый номер и все дополнительные данные возвращались в эхо-ответе. Сохранение отметки времени отправки пакета позволяет вычислить RTT при получении ответа.
В листинге 28.1[1] приведены примеры работы нашей программы. В первом используется версия IPv4, а во втором IPv6. Обратите внимание, что мы установили для нашей программы ping флаг set-user-ID (установка идентификатора пользователя при выполнении), потому что для создания символьного сокета требуются права привилегированного пользователя.
Листинг 28.1. Примеры вывода программы ping
freebsd % ping www.google.com
PING www.google.com (216.239.57.99): 56 data bytes
64 bytes from 216.239.57.99: seq=0, ttl=53, rtt=5.611 ms
64 bytes from 216.239.57.99: seq=1, ttl=53, rtt=5.562 ms
64 bytes from 216.239.57 99: seq=2, ttl=53, rtt=5.589 ms
64 bytes from 216.239.57.99: seq=3, ttl=53, rtt=5.910 ms
freebsd % ping www.kame.net
PING orange.kame.net (2001:200:0:4819:203:47ff:fea5:3085): 56 data bytes
64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=0, hlim=52, rtt=422.066 ms
64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=1, hlim=52, rtt=417.398 ms
64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=2, hlim=52, rtt=416.528 ms
64 bytes from 2001:200:0:4819.203.47ff:fea5:3085: seq=3, hlim=52, rtt=429.192 ms
На рис. 28.2 приведен обзор функций, составляющих программу ping.
Рис. 28.2. Обзор функций программы ping
Данная программа состоит из двух частей: одна половина читает все, что приходит на символьный сокет, и выводит эхо-ответы ICMP, а другая половина один раз в секунду посылает эхо-запросы ICMP. Вторая половина запускается один раз в секунду сигналом SIGALRM.
В листинге 28.2 приведен заголовочный файл ping.h, подключаемый во всех файлах программы.
Листинг 28.2. Заголовочный файл ping.h
//ping/ping.h
1 #include "unp.h"
2 #include <netinet/in_systm.h>
3 #include <netinet/in.h>
4 #include <netinet/ip_icmp.h>
5 #define BUFSIZE 1500
6 /* глобальные переменные */
7 char sendbuf[BUFSIZE];
8 int datalen; /* размер данных после заголовка ICMP */
9 char *host;
10 int nsent; /* увеличиваем на 1 для каждого sendto() */
11 pid_t pid; /* наш PID */
12 int sockfd;
13 int verbose;
14 /* прототипы функций */
15 void init_v6(void);
16 void proc_v4(char*, ssize_t, struct msghdr*, struct timeval*);
17 void proc_v6(char*, ssize_t., struct msghdr*, struct timeval*);
18 void send_v4(void);
19 void send_v6(void):
20 void readloop(void);
21 void sig_alrm(int);
22 void tv_sub(struct timeval*, struct timeval*);
23 struct proto {
24 void (*fproc)(char*, ssize_t, struct msghdr*, struct timeval*);
25 void (*fsend)(void);
26 void (*finit)(void);
27 struct sockaddr *sasend; /* структура sockaddr{} для отправки,
полученная от getaddrinfo */
28 struct sockaddr *sarecv; /* sockaddr{} для получения */
29 socklen_t salen; /* длина sockaddr{} */
30 int icmpproto; /* значение IPPROTO_xxx для ICMP */
31 } *pr;
32 #ifdef IPV6
33 #include <netinet/ip6.h>
34 #include <netinet/icmp6.h>
35 #endif
Подключение заголовочных файлов IPv4 и ICMPv41-22 Подключаются основные заголовочные файлы IPv4 и ICMPv4, определяются некоторые глобальные переменные и прототипы функций.
Определение структуры proto23-31 Для обработки различий между IPv4 и IPv6 используется структура proto. Данная структура содержит два указателя на функции, два указателя на структуры адреса сокета, размер структуры адреса сокета и значение протокола для ICMP. Глобальный указатель pr будет указывать на одну из этих структур, которая будет инициализироваться для IPv4 или IPv6.
Подключение заголовочных файлов IPv6 и ICMPv632-35 Подключаются два заголовочных файла, определяющие структуры и константы IPv6 и ICMPv6 (RFC 3542 [114]).
Функция main приведена в листинге 28.3.
Листинг 28.3. Функция main
//ping/main.c
1 #include "ping.h"
2 struct proto proto_v4 =
3 { proc_v4, send_v4, NULL, NULL, NULL, 0, IPPROTO_ICMP };
4 #ifdef IPV6
5 struct proto proto_v6 =
6 { proc_v6, send_v6, init_v6, NULL, NULL, 0, IPPROTO_ICMPV6 };
7 #endif
8 int datalen = 56; /* размер данных в эхо-запросе ICMP */
9 int
10 main(int argc, char **argv)
11 {
12 int c;
13 struct addrinfo *ai;