В продолжении к заметке о "Сокетах на канальном уровне". Для создания сокета на сетевом уровне, необходимо указывать тип 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(¶m, 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(¶m.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 *) ¶m, 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 дейтаграмма.

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