ВступлениеВ этой статье мы рассмотрим пример как сформировать и послать ICMP пакет средствами языка C на системе
FreeBSD 8.2 используя BSD RAW сокеты. Данный пример скомпилируется и под любой другой Unix-подобной системой, но для этого придётся внести некоторые поправки(см. комментарии к коду)
Модель OSIICMP —
это протокол сетевого уровня, работающий поверх IP протокола.Протокол IP содержит следующие поля:Название Длина(бит)Version 4
Header length 4
Type of Service 8
Total length 16
Identification 16
Flags 3
Offset 13
Time to Live 8
Protocol 8
Checksum 16
Source IP address 32
Destination IP address 32
Data
В поле Data будет находиться наш ICMP пакет, который в свою очередь содержит поля:type, code, id, seq, cksum. Их размеры варьируютсяПрограммная реализация Как я уже писал, мы будем использовать RAW сокеты, собирая наш пакет по байтам, т.к. обычные сокеты не предоставляют возможности использовать ICMP протокол. Поэтому, например, в
утилите ping используются RAW сокеты заместо обычных.
Приступим
/* Файл ICMP.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
/* Функция для расчёта контрольной суммы. Суммируем значения
переданных данных, разбив данные на куски по 2 байта
(u_short весит 2 байта, суммирование осуществляется в while).
Если остался 1 символ лишний - прибавляем его к получившемуся
значению. Сдвигаем результат на 16 разрядов(2 байта) вправо,
прибавляя вытесненные разряды. Инвертируем и получаем
контрольную сумму */
u_short
in_cksum(addr, len)
u_short *addr;
int len;
{
int nleft, sum;
u_short *w;
union {
u_short us;
u_char uc[2];
} last;
u_short answer;
nleft = len;
sum = 0;
w = addr;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
last.uc[0] = *(u_char *)w;
last.uc[1] = 0;
sum += last.us;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return(answer);
}
int
main(int argc, char **argv)
{
struct ip ip;
struct udphdr udp;
struct icmp icmp;
int sd;
const int on = 1;
struct sockaddr_in sin;
u_char *packet;
/* Выделяем память для нашего пакета */
packet = (u_char *)malloc(60);
/* Длина заголовков(включая опции): 32 бита(4 байта) каждый.
Учитывая что мы не будем посылать никаких опций, длина IP
пакета будет равна 20 байт, поэтому нам нужно (20 / 4 = 5) */
ip.ip_hl = 0x5;
/* Версия протокола 4(IPv4) */
ip.ip_v = 0x4;
/* Приоритет пакета */
ip.ip_tos = 0x0;
/* Общая длина пакета. */
ip.ip_len = 60; /* В linux htons(60) */
/* Уникальный идентефикатор пакета, посланного хостом */
ip.ip_id = htons(12830);
/* Смещение пакета. Ставим 0x0, т.к. в данном случае
не предусмотрено фрагментации */
ip.ip_off = 0x0;
/* Время жизни, при проходе через маршрутизатор уменьшается
на единицу, если равно нулю то отбрасывается маршрутизатором */
ip.ip_ttl = 64;
/* Используемый протокол */
ip.ip_p = IPPROTO_ICMP;
/* Устанавливаем в 0 до того как считать контрольную сумму.
Здесь контрольная сумма считается только для IP пакета,
протоколы верхнего уровня имеют собственные поля для контрольной
суммы, которая должна быть посчитана отдельно */
ip.ip_sum = 0x0;
/* Адрес отправителя. Если вписать в следующие 2 поля свой адрес,
то пакет пойдёт через интерфейс lo0 */
ip.ip_src.s_addr = inet_addr("172.12.129.30");
/* Адрес получателя */
ip.ip_dst.s_addr = inet_addr("172.12.129.30");
/* Считаем контрольную сумму IP пакета. Некоторые драйверы
поддерживают автоматическую разгрузку контрольной суммы, чтобы поля
контрольной суммы не заполнялись нулями, её нужно отключить
(ifconfig <имя интерфейса> -rxcsum -txcsum) */
ip.ip_sum = in_cksum((unsigned short *)&ip, sizeof(ip));
/* Тело IP пакета готово, копируем его в начало нашего пакета */
memcpy(packet, &ip, sizeof(ip));
/* ICMP пакет. Тип */
icmp.icmp_type = ICMP_ECHO;
/* Код 0 - request */
icmp.icmp_code = 0;
/* ID - Любое число */
icmp.icmp_id = htons(1000);/*В Linux htons не требуется*/
/* Последовательный номер */
icmp.icmp_seq = 0;
/* Считаем контрольную сумму */
icmp.icmp_cksum = 0;
icmp.icmp_cksum = in_cksum((unsigned short *)&icmp, 8);
/* Копируем ICMP в наш пакет сразу после IP */
memcpy(packet + 20, &icmp, 8);
/* Пакет готов, теперь нужно подготовить сокет */
if ((sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
perror("socket error");
exit(1);
}
/* Сообщаем, что заголовок уже вложен */
if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
perror("setsockopt error");
exit(1);
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = ip.ip_dst.s_addr;
/* Вместо send используем sendto, т.к. соединение не было установлено */
if (sendto(sd, packet, 60, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0) {
perror("sendto error");
exit(1);
}
return 0;
}
Должно получиться примерно следующее:
Первый терминал:
# gcc ICMP.c -o ICMP
ICMP.c:122:2: warning: no newline at end of file
# ~/ICMP
На втором терминале tcpdump ругается что контрольная сумма не правильная(см. ниже)
Отключаем разгрузку и запускаем ещё раз
# ifconfig lo0 -rxcsum -txcsum
# ~/ICMP
Не ругается
Снова включаем
# ifconfig lo0 rxcsum txcsum
# ~/ICMP
Второй терминал:
#tcpdump -i lo0 -vvv 'icmp'
tcpdump: listening on lo0, link-type NULL (BSD loopback), capture size 96 bytes
21:21:00.057656 IP (tos 0x0, ttl 64, id 12830, offset 0, flags [none], proto ICMP (1), length 60, bad cksum 0 (->9e25)!)
<IP> > <IP>: ICMP echo request, id 1000, seq 0, length 40
21:21:00.057667 IP (tos 0x0, ttl 64, id 50209, offset 0, flags [none], proto ICMP (1), length 60, bad cksum 0 (->c22)!)
<IP> > <IP>: ICMP echo reply, id 1000, seq 0, length 40
21:21:19.243796 IP (tos 0x0, ttl 64, id 12830, offset 0, flags [none], proto ICMP (1), length 60)
<IP> > <IP>: ICMP echo request, id 1000, seq 0, length 40
21:21:19.243807 IP (tos 0x0, ttl 64, id 50260, offset 0, flags [none], proto ICMP (1), length 60)
<IP> > <IP>: ICMP echo reply, id 1000, seq 0, length 40
21:21:35.837038 IP (tos 0x0, ttl 64, id 12830, offset 0, flags [none], proto ICMP (1), length 60, bad cksum 0 (->9e25)!)
<IP> > <IP>: ICMP echo request, id 1000, seq 0, length 40
21:21:35.837049 IP (tos 0x0, ttl 64, id 50265, offset 0, flags [none], proto ICMP (1), length 60, bad cksum 0 (->bea)!)
<IP> > <IP>: ICMP echo reply, id 1000, seq 0, length 40