Тема: Сирі сокети. ICMP
Колись я роздумував "Як ж працює ping". Тоді ж шукаючи відповідь я надибав як працювати з Сирими сокетами.
Сирий сокет це тип сокету коли всі заголовки створюються вручну. Це цікава штука, але потребує root-прав.
Так багато писати я не люблю то поясню все на прикладі
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
/* Функція для обчислення чексуми */
unsigned short csum(unsigned short *buf, int nwords)
{
register unsigned long sum;
for(sum=0; nwords>0; nwords--)
sum += *buf++;
sum = (sum >> 16) + (sum &0xffff);
sum += (sum >> 16);
return (unsigned short)(~sum);
}
/* Функція для визанчення власного IP */
int get_self_IP(in_addr_t addr, const int port)
{
struct sockaddr_in serv;
int sock = socket ( AF_INET, SOCK_DGRAM, 0);
//Socket could not be created
if(sock < 0)
{
return -1;
}
serv.sin_family = AF_INET;
serv.sin_addr.s_addr = addr;
serv.sin_port = htons(port);
int err = connect( sock , (struct sockaddr*) &serv , sizeof(serv) );
struct sockaddr_in name;
socklen_t namelen = sizeof(name);
err = getsockname(sock, (struct sockaddr*) &name, &namelen);
close(sock);
return name.sin_addr.s_addr;
}
int ICMP(in_addr_t addr, char* data, int datalen)
{
/* Буфери для збереження даних */
char outbuffer[1500];
char inbuffer[1500];
/* Налаштовуємо вказівники */
struct iphdr *sendip = (struct iphdr *) outbuffer,
*recvip = (struct iphdr *) inbuffer;
struct icmphdr *sendicmp = (struct icmphdr *) (outbuffer + sizeof(struct iphdr)),
*recvicmp = (struct icmphdr *) (inbuffer + sizeof(struct iphdr));
struct sockaddr_in sin, din;
struct in_addr buff_for_addr;
int one = 1;
const int *val = &one;
memset(outbuffer, 0, 1500);
memset(inbuffer, 0, 1500);
/* Створюємо сокет
SOCK_RAW - позначення сирого сокету
IPPROTO_ICMP - тип пакету
*/
int sd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
if(sd < 0)
{
perror("socket() error");
exit(-1);
}
else
printf("socket() - Використання SOCK_RAW сокету і ICMP протокол OK.\n");
sin.sin_family = AF_INET;
din.sin_family = AF_INET;
/* Налаштовуємо адреси */
sin.sin_addr.s_addr = get_self_IP(addr, 80);
din.sin_addr.s_addr = addr;
/* Налаштовуємо IP заголовок */
sendip->ihl = 5;
sendip->version = 4;
sendip->tos = 0;
sendip->tot_len = sizeof(struct iphdr) + sizeof(struct icmphdr) + 6;
sendip->frag_off = 0x40;
sendip->id = getpid();
sendip->ttl = 64;
sendip->protocol = IPPROTO_ICMP; // Тип наступного заголовку
sendip->saddr = sin.sin_addr.s_addr;
sendip->daddr = din.sin_addr.s_addr;
/* Налаштування ICMP заголовку */
sendicmp->type = ICMP_ECHO;
sendicmp->code = 0;
sendicmp->un.echo.id = getpid();
sendicmp->un.echo.sequence = 1;
/* Записуємо передані дані в секцію даних */
memcpy(outbuffer + sizeof(struct iphdr) + sizeof(struct icmphdr), data, datalen);
/* Вказуємо, що ми самі "пишемо" заголовок */
if(setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(one)) < 0)
{
perror("setsockopt()");
exit(-1);
}
else
printf("setsockopt() OK.\n");
while(1)
{
/* Записуємо чексуми,
я записую 0 з початку, бо в мене інакше змінні не перезаписувались*/
sendip->check = 0;
sendip->check = csum((unsigned short *)outbuffer, sizeof(struct iphdr) + sizeof(struct icmphdr) + datalen);
sendicmp->checksum = 0;
sendicmp->checksum = csum((unsigned short *)sendicmp, sizeof(struct icmphdr) + datalen);
/* Надсилаємо пакет */
if(sendto(sd, outbuffer, sendip->tot_len, 0, (struct sockaddr *)&din, sizeof(din)) < 0)
{
perror("sendto() error");
exit(-1);
}
else
printf("sendto() is OK.\n");
/* Приймаємо пакет */
int len = sizeof(din);
if (recvfrom(sd, inbuffer, 1500, 0, (struct sockaddr *) &din, &len) < 0)
{
perror("recvfrom() error");
exit(-1);
}
else
{
/* Перевіримо чи це наш пакет */
if (recvicmp->un.echo.id == sendicmp->un.echo.id)
{
buff_for_addr.s_addr = recvip->saddr;
printf("Package return from %s, seq = %d, ttl = %d\n", inet_ntoa(buff_for_addr), recvicmp->un.echo.sequence, recvip->ttl);
sendicmp->un.echo.sequence++;
}
}
}
close(sd);
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("- Invalid parameters!!!\n");
printf("- Usage %s <target hostname/IP>\n", argv[0]);
exit(-1);
}
in_addr_t addr;
struct hostent *info;
if (info = gethostbyname(argv[1])) // Отримуємо дані про хост
{
addr = ((struct in_addr*)(info->h_addr_list[0]))->s_addr;
}
else
addr = inet_addr(argv[1]); // Якщо нічого не знайдено то записуєи IP як є
ICMP(addr, "drWoZD", 6);
return 0;
}
P.S. Сподіваюсь хтось дізнався щось нове
P.P.S. Якщо щось незрозуміло пишіть