пятница, 2 сентября 2011 г.

Сокеты на сетевом уровне

В продолжении к заметке о "Сокетах на канальном уровне". Для создания сокета на сетевом уровне, необходимо указывать тип SOCK_RAW:

sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

В таком случае ethernet кадр будет собран ядром, и соответственно в поле "протокол" (h_proto) ethernet кадра будет указан IP-протокол (0x0800). Очевидно, что осуществить обмен по низкоуровневым протоколам уже не удастся, поскольку для обмена по тому же ARP, в поле "протокол" ethernet кадра должен быть указан 0x0806. Тем не менее, используя сокеты сетевого уровня, вы можете собрать ICMP (протокол сокета IPPROTO_ICMP), IGMP (протокол сокета IPPROTO_IGMP) пакеты.

Сбор пакетов начиная с заголовка IP можно осуществлять указав в качестве протокола сокета IPPROTO_RAW, а также параметр IP_HDRINCL:

int en = 1;
int sock;
sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &ena, sizeof(ena));

Подобные сокеты необходимы, когда требуется реализовать обмен по протоколу, который не использует протоколы UDP и TCP, и не поддерживается ядром, например, протокол OSPF (протокол маршрутизации).

В качестве примера работы с символьными сокетами создадим простое приложение, которое отправляет upd дейтаграмму, самостоятельно заполняя ip и udp заголовки.

Первоначально, заполним ip пакет, структура которого определена в файле ip.h:

struct hostent      *src_host, *dst_host;
struct ip           *iph;       // указатель на IP заголовок
char                datagram[1024];

... ... ...

// Инициализируем все структуры
memset(&src_host, 0, sizeof(src_host));
memset(&dst_host, 0, sizeof(dst_host));
memset(&param, 0, sizeof(param));
memset(&datagram, 0, sizeof(datagram));

dst_host = gethostbyname(argv[1]);  // IP адрес назначения
src_host = gethostbyname(argv[2]);  // исходящий IP адрес

// размещаем и заполняем заголовок IP-дейтаграммы
iph = (struct ip *) datagram;
iph->ip_v = 4;      // версия проток. - IPv4
iph->ip_hl = 5;     // длина заголовка IP пакета в 32-х разр. словах.
iph->ip_tos = 0;    // тип службы - обычно игнорируется
iph->ip_id = 0;     // игнорируем, т.к. не фрагментируем
iph->ip_off = 0;    // нет смещения, т.к. не фрагментируем
iph->ip_ttl = 64;   // время жизни - 64 перехода
iph->ip_p = 17;      // номер протокола - используем IP

// заполняем адреса отправителя и получателя соответственно
memcpy(&(iph->ip_src.s_addr), src_host->h_addr, src_host->h_length);
memcpy(&(iph->ip_dst.s_addr), dst_host->h_addr, dst_host->h_length);

/* Размер всей IP дейтаграммы и контрольная сумма обычно заполняется ядром.
iph->ip_len = 20;
iph->ip_sum = checksum((short unsigned int *) datagram, 10); */


Отмечу, что номера протоколов для IP заголовка можно посмотреть в RFC 1700, кроме того, обращу внимание на то, что IP адрес источника можно указывать ненастоящий - правильность этого поля ядром проверяться не будет, но в этом случае не стоит потом ждать ответа.

После того, как заполнен IP заголовок, разместим за ним заголовок UDP, определив его структуру:

// структура UDP заголовка
struct udp {
    unsigned short  dsp;    // порт источника
    unsigned short  scp;    // порт назначения
    unsigned short  len;    // длина UDP
    unsigned short  sum;    // контрольная сумма UDP
};

... ... ...

char *msg = "Hello World";

// размещаем и заполняем udp заголовок
udph = (struct udp *) (datagram + sizeof(struct ip));
udph->dsp = htons(14350);   // порт назначения
udph->scp = htons(14350);   // порт источника
udph->sum = 0;      // Для UDP не обязательна контр. сумма
    
// строку с приветствием добавляем в данные UDP пакета
strcpy((char *)(datagram + sizeof(struct ip) + sizeof(struct udp)), msg);
udph->len = htons(sizeof(struct udp) + strlen(msg)); // длина UDP

Стоит отметить, что контрольная сумма для UDP дейтаграммы не является обязательной. Теперь, когда пакет собран, его необходимо отправить:

// адрес сокета
memcpy(&param.sin_addr.s_addr, dst_host->h_name, dst_host->h_length);
param.sin_family = AF_INET;

// создаем сокет на сетевом уровне (протокол IP)
ena = 1;
sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &ena, sizeof(ena));

    
if (sendto(sock, &datagram, sizeof(struct ip) + sizeof(struct udp), 0, (struct sockaddr *) &param, sizeof(param)) < 0) {
    printf("IP Packet was sent unsuccessfully...\n");
}
else { printf("IP Packet was sent (size %d)\n", iph->ip_len); 

Запустим анализатор трафика Wireshark и убедимся в том, что дейтаграмма была успешно отправлена - рис. 1.

Рис. 1. Отправленная UDP дейтаграмма.



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

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