27.2.4. Функции для работы с протоколом ICMP
Для работы с протоколом ICMP существует 12 основных функций. Все эти функции описаны в файле /usr/src/linux/net/ipv4/icmp.c. У вас нет этого файла? Тогда установите исходники ядра (странно, почему вы до сих пор этого не сделали).
♦ icmp_address() — отправка ответа на запрос о маске адреса;
♦ icmp_discard() — удаляет ICMP-пакет;
♦ icmp_echo() — эхо-запрос;
♦ icmp_init() — инициализирует служебные подпрограммы протокола ICMP в операционной системе;
♦ icmp_out_count() — увеличивает счетчик отправленных пакетов;
♦ icmp_rcv() — прием ICMP-пакета;
♦ icmp_redirect() — отправка сообщения переадресации;
♦ icmp_send() — отправка ICMP-сообщения;
♦ icmp_timestamp() — ответ на запрос о времени создания;
♦ icmp_unreach() — отправляет сообщение об ошибке;
♦ xrlim_allow() — решает, отправлять ли ICMP-пакет или нет;
♦ xrlim_init() — ограничение скорости передачи ICMP-пакетов (в версии ядра 2.0).
27.2.4.1. Технические подробности
Прежде чем перейти к рассмотрению функций ICMP, разберемся, как же ICMP-пакеты принимаются операционной системой. Собственно, ICMP-пакет принимается операционной системой Linux так же, как и любой другой пакет. Драйвер сетевой платы (или другого сетевого устройства) собирает полный пакет данных, затем он строит структуру sk_buff.
Листинг 27.1. Структура sk_buff
struct sk_buff {
/* Эти два члена должны быть первыми */
struct sk_buff* next; /* Следующий буфер в списке*/
struct sk_buff* prev;/* Предыдущий буфер в списке*/
struct sk_buff_head * list; /* "Голова" списка */
struct sock *sk; /* Сокет */
struct timeval stamp; /* Время прибытия пакета */
struct net_device *dev; /* Сетевое устройство */
/* Заголовок транспортного уровня */
union {
struct tcphdr *th;
struct udphdr *uh;
struct icmphdr *icmph;
struct igmphdr* igmph;
struct iphdr *ipiph;
struct spxhdr *spxh;
unsigned char *raw;
} h;
/* Заголовок сетевого уровня */
union {
struct iphdr *iph;
struct ipv6hdr* ipv6h;
struct arphdr *arph;
struct ipxhdr *ipxh;
unsigned char *raw;
} nh;
union {
struct ethhdr *ethernet;
unsigned char *raw;
} mac;
struct dst_entry *dst;
char cb[48];
unsigned int len; /* Длина данных */
unsigned int data_len;
unsigned int csum; /* Контрольная сумма */
unsigned char __unused, /* He используется */
cloned, /* Заголовок должен клонироваться */
pkt_type, /* Класс пакета */
ip_summed; /* контрольная сумма IP */
__u32 priority; /* Приоритет пакета */
atomic_t users; /* Счетчик пользователей — см.
datagram.c, tcp.c */
unsigned short protocol; /* Протокол пакета */
unsigned short security; /* Уровень безопасности */
unsigned int truesize; /* Размер буфера */
unsigned char *head; /* Заголовок буфера */
unsigned char *data; /* Указатель заголовка данных */
unsigned char *tail; /* Указатель "хвоста" */
unsigned char *end; /* Конечный указатель */
void (*destructor)(struct sk_buff*);
#ifdef CONFIG_NETFILTER
unsigned long nfmark;
/* Cache info */
__u32 nfcache;
/* Ассоциированное соединение */
struct nf_ct_info *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;
#endif
#endif /* CONFIG_NETFILTER*/
#if defined(CONFIG_HIPPI)
union {
__u32 ifield;
} private;
#endif
#ifdef CONFIG_WET_SCHED
__u32 tc_index; /* Индекс контроля трафика */
#endif
};
Данная структура описана в файле /usr/src/linux/include/linux/skbuff.h. После формирования структуры sk_buff она передается драйвером функции netif_rx.
int netif_rx(struct sk_buff *skb);
Функция netif_rx() описана в файле /usr/src/linux/net/core/dev.c. Она получает пакет от драйвера сетевого устройства и ставит его в очередь (очередь называется backlog) протокола высшего уровня. Функция возвращает следующие значения:
♦ NET_RX_SUCCESS — пакет удачно поставлен в очередь;
♦ NET_RX_CN_LOW — имеется небольшая «пробка» при постановке пакета в очередь, но скоро она «рассосется»;
♦ NET_RX_CN_MOD — «пробка» чуть больше — средней «длины»;
♦ NET_RX_CN_HIGH — очень большая «пробка»;
♦ NET_RX_DROP — пакет был удален.
Если в очереди backlog находятся более 300 пакетов, новый пакет будет удален без какого-либо предупреждения.
Драйвер сетевого устройства при формировании структуры sk_buff устанавливает ее поле protocol. Функция netif_rx(), как уже было сказано, передает пакет «наверх», затем функция net_bh() использует значение поля protocol, установленное драйвером, для вызова соответствующей протоколу программы и передает этой программе пакет. Для протокола ICMP такой программой является функция icmp_rcv(), описанная в файле /usr/src/linux/net/ipv4/icmp.с.
int icmp_rcv(struct sk_buff *skb);
Функция icmp_rcv выполняет следующие действия:
1. Увеличивает значение счетчика входящих пакетов ICMP_INC_STATS_BH(IcmpInMsgs).
2. Удаляет пакет, если его размер слишком мал.
3. Проверяет контрольную сумму: если она ошибочна, увеличивает счетчик некорректных пакетов.
4. Проверяет тип ICMP-пакета. Если тип пакета превышает максимальное значение 18, то пакет удаляется. При этом увеличивается счетчик некорректных пакетов.
5. Вызывает функцию-обработчик ICMP-сообщения в зависимости от значения типа сообщения.
Соответствие номеров типа служебным функциям (описаны в файле icmp.c) Таблица 27.9
Тип Функция Описание 0, 1, 2, 6, 7, 9, 10, 12, 14-16, 18 void icmp_discard(struct sk_buff *skb) Удаляет пакет 3, 4, 11 void icmp_unreach(struct sk_buff *skb) Данная функция определяет максимальный размер модуля передачи (MTU, Maximum Transmission Unit), а также передает информацию об ошибке любому модулю, которому нужна эта информация 5 void icmp_redirect(struct sk_buff *skb) Пакет переадресации, который означает примерно следующее: «Не посылайте мне пакет по адресу xxx.xxx.ххх.xxx, потому что он будет отправлен обратно» 8 void icmp_echo(struct sk_buff *skb) Функция для отправки эхо-запроса. Эхо-ответ (тип 0) отправляется ядром автоматически (если вы не отключили эту возможность) 13 void icmp_timestamp(struct sk_buff *skb) Запрос о времени создания пакета — один из самых эффективных методов измерения производительности глобальных сетей в реальном времени. Метод заключается в следующем: на тестируемый узел посылаются небольшие пакеты, которые сразу же удаляются. Информация из последнего пакета копируется в пакет эхо-ответа, в этот пакет также вставляется информация о текущем времени. После этого пакет ставится в очередь как обычно 17 void icmp_address (struct sk_buff *skb) Отправка ответа на запрос о маске адреса
Рассмотренные выше функции обрабатывают входящие ICMP-пакеты, Но мы ведь можем не только принимать ICMP-пакеты, но и передавать, поэтому есть также и «исходящие» функции.
Для отправки ICMP-сообщений используется функция
void icmp_send(struct sk_buff *skb_in, int type, int code,
u32 info);
Данная функция отправляет сообщения типа type, с кодом code и телом info.
После отправки пакета неплохо бы проверить, будет ли он реально отправлен. Это можно сделать с помощью функции
int xrlim_allow(struct dst_entry *dst, int timeout);
Эта функция определяет, отправлять или нет текущий пакет из очереди. Если пакет должен быть удален, функция возвращает 0, иначе — 1.
После отправки ICMP-пакета нужно увеличить счетчик отправленных пакетов. Это можно сделать с помощью функции icmp_out_count():
void icmp_out_count(int type);
Данная функция увеличивает счетчик оправленных пакетов типа type.
На этом обзор протокола ICMP в Linux заканчивается и мы переходим к рассмотрению механизма гнезда (сокета), с помощью которого можно реализовать двунаправленный обмен данными между двумя компьютерами.