linux icmp echo socket发包学习
socket创建函数原型
int socket(int protofamily, int type, int protocol);//返回sockfd
参数说明:
protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
原始套接字与标准套接字(标准套接字指的是流套接字SOCK_STREAM和数据报套接字SOCK_DGRAM)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接字。原始套接字主要用于一些协议的开发,可以进行比较底层的操作。
protocol:常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议,这里我们发送icmp报文,使用IPPROTO_ICMP。
setsockopt函数
setsockopt函数用于任意协议、任意层次、任意选项的设定或者改变。这是一个通用的函数,可以对套接字的一些特性进行设置。最常见的用途是设置超时时间,或者设置套接字为非阻塞模式。函数原型如下:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数说明:
- sockfd:标识一个套接字的描述字。
- level:选项定义的层次:SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
- optname:需要访问的选项名。
- optval:对于getsockopt(),指针指向返回选项的值;对于setsockopt(),指针指向包含新选项值的缓冲。
- optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,返回实际选项的长度;对于setsockopt(),现选项的长度。
setsockopt函数返回值为0表示成功,返回-1表示失败,错误原因存于errno中。
如果不使用该函数绑定发送端口,内核会自动查找路由决定发送报文的网络设备。
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ICMP_HEADER_SIZE 8
unsigned short calculate_checksum(unsigned short *buffer, int length) {
unsigned long checksum = 0;
while (length > 1) {
checksum += *buffer++;
length -= sizeof(unsigned short);
}
if (length) {
checksum += *(unsigned char *)buffer;
}
checksum = (checksum >> 16) + (checksum & 0xffff);
checksum += (checksum >> 16);
return (unsigned short)(~checksum);
}
int main(int argc, char **argv)
{
int ret;
int i;
int sockfd;
int source_port = 1024;
int packet_num = 3;
const char message[] = "send a message";
const char interface[] = "eth0";
const char ip_address[] = "192.168.6.154";
printf("message len:%d\n", strlen(message));
// 创建原始套接字
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd < 0)
{
printf("create a icmp socket error\n");
}
//绑定eth0
ret = setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, interface, strlen(interface));
if (ret < 0) {
perror("setsockopt SO_BINDTODEVICE");
exit(EXIT_FAILURE);
}
// 准备一个空的缓冲区,存放 ICMP 报文
char buffer[IP_MAXPACKET];
memset(buffer, 0, IP_MAXPACKET);
// 填充 ICMP 报文头部
struct icmphdr *icmp_header = (struct icmphdr *)buffer;
icmp_header->type = ICMP_ECHO;
icmp_header->code = 0;
icmp_header->un.echo.id = getpid();
icmp_header->un.echo.sequence = 0;
//icmp_header->un.frag.frag_off |= htons(IP_DF);
printf("pid:%d\n", getpid());
// 填充 ICMP 报文数据 (payload)
char *payload = buffer + ICMP_HEADER_SIZE;
strcpy(payload, message);
icmp_header->checksum = 0;
icmp_header->checksum = calculate_checksum((unsigned short *)icmp_header, ICMP_HEADER_SIZE + strlen(message));
// 准备目标主机的地址结构
struct sockaddr_in dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
if (inet_pton(AF_INET, ip_address, &dest_addr.sin_addr) <= 0) {
printf("Invalid destination address: %s\n", "ip_address");
return 1;
}
// 指定端口号
dest_addr.sin_port = htons(source_port);
// 发送 ICMP 报文
for (i = 0; i < packet_num; i++)
{
if (sendto(sockfd, buffer, ICMP_HEADER_SIZE + strlen(message), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
printf("Failed to send ICMP packet.\n");
return 1;
}
}
close(sockfd);
return 0;
}