четверг, 17 ноября 2011 г.

Протокол ICMP

Вступление
В этой статье мы рассмотрим пример как сформировать и послать ICMP пакет средствами языка C на системе FreeBSD 8.2 используя BSD RAW сокеты.  Данный пример скомпилируется и под любой другой Unix-подобной системой, но для этого придётся внести некоторые поправки(см. комментарии к коду)


Модель OSI
ICMP это протокол сетевого уровня, работающий поверх 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

Комментариев нет:

Отправить комментарий