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