目录
- Linux中的原始套接字(Raw Socket)是一种高级套接字类型,允许应用程序直接访问网络协议栈,发送和接收自定义的网络数据包。使用原始套接字,你可以实现各种网络工具、网络协议分析和网络攻防等功能。
-
- 下面是使用原始套接字的一般步骤:
-
- 1 创建原始套接字:通过调用socket()函数创建一个原始套接字。指定参数AF_PACKET表示使用Packet套接字族,参数SOCK_RAW表示使用原始套接字类型。
-
- int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
-
- 2
- 绑定套接字到网络设备:使用bind()函数将套接字绑定到特定的网络设备上。你需要指定网络设备的名称或索引。可以使用ifconfig命令或者ioctl()函数来获取设备名称和索引。
-
- struct sockaddr_ll sa;
- memset(&sa, 0, sizeof(sa));
- sa.sll_family = AF_PACKET;
- sa.sll_protocol = htons(ETH_P_ALL);
- sa.sll_ifindex = if_nametoindex("eth0");
- bind(sockfd, (struct sockaddr*)&sa, sizeof(sa));
-
- 3 构造自定义数据包:使用结构体来构造自定义的网络数据包,如以太网帧(struct ethhdr)和IP数据报头(struct iphdr)。在构造数据包时,你需要设置正确的协议头信息和有效载荷。
-
- struct ethhdr ether_header;
- struct iphdr ip_header;
- char payload[100];
-
- // 构建以太网帧、IP数据报头和有效载荷
- // ...
-
- 4 发送和接收数据包:使用sendto()函数发送数据包,指定目标地址和端口。使用recvfrom()函数接收数据包,获取发送者的信息。
-
- // 发送数据包
- sendto(sockfd, ðer_header, sizeof(ether_header) + sizeof(ip_header) + payload_length, 0, (struct sockaddr*)&sa, sizeof(sa));
-
- // 接收数据包
- struct sockaddr_ll sa_recv;
- socklen_t sa_len = sizeof(sa_recv);
- recvfrom(sockfd, recv_buffer, sizeof(recv_buffer), 0, (struct sockaddr *)&sa_recv, &sa_len);
-
- 5 关闭套接字:当你完成使用原始套接字时,记得使用close()函数关闭套接字。
-
- close(sockfd);
-
- 请注意,在使用原始套接字时需要具有足够的权限,通常需要以root用户身份运行程序。此外,使用原始套
- 接字需要对网络协议有深入的了解,并且要小心操作,以避免对网络造成不良影响。
tcp_all.c 接收所用链路层的数据包,但是以TCP为主(实现功能类似wireshark)
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define MTU 1500
-
- int main()
- {
- /* 定义变量 */
- int sockfd, len;
- uint8_t buf[MTU]={};
- uint16_t ether_type;
-
- /*
- struct iphdr是一个定义在
中的IP包头结构体,用于表示IPv4协议的包头信息。 - struct tcphdr是一个定义在
中的TCP包头结构体,用于表示TCP协议的包头信息。 - struct ether_header是一个定义在
中的以太网帧头结构体,用于表示以太网帧头的信息。 - */
- struct iphdr *iph; //IP包头
- struct tcphdr *tcph;//TCP包头
- struct ether_header *eth;
-
- /* 创建一个链路层原始套接字 */
- if( (sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) ) < 0){
- perror("socket");
- return 0;
- }
- printf("sockfd = %d\n", sockfd);
-
- /* 接收(只接收TCP数据协议)并处理IP数据报 */
- while(1)
- {
- /* 接收包含TCP协议的IP数据报 */
- len = recvfrom(sockfd, buf, sizeof(buf),0,NULL,NULL);
-
- eth = (struct ether_header *)buf;
- ether_type = htons(eth->ether_type);
- switch(ether_type){
- case ETHERTYPE_IP:
- printf("IP协议\n");
- break;
- case ETHERTYPE_ARP:
- printf("ARP协议\n");
- break;
- case ETHERTYPE_LOOPBACK:
- printf("loop back\n");
- break;
- default:
- printf("其他协议 %x\n",eth->ether_type);
- }
- if(ether_type != ETHERTYPE_IP) //如果是IP包才往下执行
- continue;
-
- /* 打印源IP和目的IP */
- iph = (struct iphdr *)(buf+14);
- if(iph->protocol != IPPROTO_TCP) //如果是ip包并且是TCP的包才往下执行并打印
- continue;
- printf("源IP:%s\n",inet_ntoa(*(struct in_addr *)&iph->saddr) );
- printf("目的IP%s\n",inet_ntoa(*(struct in_addr *)&iph->daddr) );
-
- /* 打印TCP包头的源端口号和目的端口号 */
- tcph = (struct tcphdr *)(buf+14+iph->ihl*4);
- printf("%hu--->", ntohs(tcph->source));
- printf("%hu\n", ntohs(tcph->dest));
-
- /* 打印TCP数据段的长度 */
- printf("TCP首部长度:%d\n", tcph->doff*4);
- }
- //关闭套接字
- close(sockfd);
- return 0;
- }
运行效果:

后期改进,也可以更细的去打印各类协议,详细包内容。
send_ping.c
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- uint16_t SetChetSum(uint16_t *buf, int size);
- void Set_IcmpH(struct icmphdr *icmph, int size);
-
- #define MSG_SIZE 40
-
- int main(int argc, char *argv[])
- {
- /* 定义变量 */
- int fd;
- char buf[MSG_SIZE]={};
-
- if(argc < 3)
- {
- printf("%s
\n" , argv[0]); - exit(EXIT_FAILURE);
- }
-
- /* 创建原始套接字 */
- if((fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
- {
- perror("socket");
- return 0;
- }
- struct sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons(atoi(argv[2]));
- if(inet_aton(argv[1], &sin.sin_addr) == 0)
- {
- printf("Invalid address\n");
- exit(EXIT_FAILURE);
- }
- Set_IcmpH((struct icmphdr *)&buf, MSG_SIZE);
- sendto(fd, buf, MSG_SIZE, 0, (struct sockaddr *)&sin, sizeof(sin));
- close(fd);
- return 0;
-
-
- }
-
- void Set_IcmpH(struct icmphdr * icmph, int size)
- {
- static int seq;
- icmph->type = ICMP_ECHO; //icmp宏定义
- icmph->code = 0; //每个icmp的编号,发一次,简化逻辑
- icmph->un.echo.id = getpid();
- icmph->un.echo.sequence = seq++;
- icmph->checksum = SetChetSum((uint16_t *)icmph, MSG_SIZE);//校验和
-
- }
-
- uint16_t SetChetSum(uint16_t *buf, int size)
- {
- uint32_t checksum = 0;
- while(size > 1)
- {
- checksum += *buf++;
- size -= 16;
- }
-
- if(size)
- checksum += *(unsigned char *)buf;
- checksum = (checksum >> 16) + (checksum & 0xffff);
- checksum += (checksum >> 16);
- return (uint16_t)(~checksum);
-
- }
实验效果

原始套接字(Raw Socket)和标准套接字(Standard Socket)是在网络编程中使用的两种不同类型的套接字。
原始套接字(Raw Socket): 原始套接字允许应用程序直接访问网络协议栈,可以发送和接收原始的网络数据包。使用原始套接字,开发者可以自定义协议头部信息,以及对底层网络数据包进行更底层的控制和处理。它提供了更高级别的网络访问权限,但需要更高的权限级别(如管理员或特权用户)来使用。(整个数据包,包括IP的包头、TCP包头)
标准套接字(Standard Socket): 标准套接字是通过操作系统提供的网络套接字API进行通信的一种方式,使用TCP(Transmission Control Protocol)或UDP(User Datagram Protocol)等传输协议。标准套接字隐藏了底层网络协议的细节,提供了更高层次的抽象和简化的网络编程接口,使得开发人员可以更方便地进行网络通信和应用程序开发。
区别:
在实际应用中,一般情况下使用标准套接字就能满足大部分的网络通信需求,而原始套接字主要由网络工具和专业开发者使用。
tcp.c (主要用于网络工具的开发)
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define MTU 1500
-
- int main()
- {
- /* 定义变量 */
- int sockfd = -1, len, datalen, i;
- uint8_t buf[MTU]={}, *data;
-
- struct iphdr *iph; //IP包头
- struct tcphdr *tcph;//TCP包头
- struct winsize size;
-
- /* 创建一个原始套接字 */
- if( (sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP) ) < 0)
- {
- perror("socket");
- return 0;
- }
- printf("sockfd = %d\n", sockfd);
-
- /* 接收(只接收TCP数据协议)并处理IP数据报 */
- while(1)
- {
- /* 接收包含TCP协议的IP数据报 */
- len = recvfrom(sockfd, buf, sizeof(buf),0,NULL,NULL);
- printf("IP数据报长度 = %d\n", len);
-
- /* 打印源IP和目的IP */
- iph = (struct iphdr *)buf; //首地址是一样的,强转成ip数据报结构体
- printf("源IP:%s",inet_ntoa(*(struct in_addr *)&iph->saddr) );
- printf("目的IP%s\n",inet_ntoa(*(struct in_addr *)&iph->daddr) );
-
- /* 打印TCP包头的源端口号和目的端口号 */
- tcph = (struct tcphdr *)(buf+iph->ihl*4); //强转buf中tcp部分
- printf("%hu--->", ntohs(tcph->source));
- printf("%hu\n", ntohs(tcph->dest));
-
- /* 打印TCP数据段的长度 */
- printf("TCP首部长度:%d\n", tcph->doff*4);
- if(iph->ihl*4+tcph->doff*4 < len) { //tcp数据报里面有没有放数据
- data = buf + iph->ihl*4 + tcph->doff*4;
- datalen = len - iph->ihl*4 + tcph->doff*4;
- ioctl(STDIN_FILENO,TIOCGWINSZ,&size); //terminal 结构体
- for(i = 0; i < size.ws_col; i++) //显示一行 =
- putchar('=');
- putchar('\n');
- printf("TCP数据字符:\n");
- for(i = 0; i < size.ws_col; i++)
- putchar('=');
- putchar('\n');
- for(i = 0; i < datalen-1; i++) {
- printf("%c", data[i]);
- }
- for(i = 0; i < size.ws_col; i++)
- putchar('=');
- putchar('\n');
- printf("TCP数据16进制:\n");
- for(i = 0; i < size.ws_col; i++)
- putchar('=');
- putchar('\n');
- for(i = 0; i < datalen-1; i++){
- printf("%x ", data[i]);
- }
- putchar('\n');
- for(i = 0; i < size.ws_col; i++)
- putchar('=');
- putchar('\n');
- }
- }
- //关闭套接字
- close(sockfd);
- return 0;
- }
执行效果

IP协议包头结构体
/usr/include/linux/ip.h
- struct iphdr {
- #if defined(__LITTLE_ENDIAN_BITFIELD) //小段模式
- __u8 ihl:4, //位域操作,两个合起来占8个字节
- version:4;
- #elif defined (__BIG_ENDIAN_BITFIELD) //大端模式
- __u8 version:4,
- ihl:4;
- #else
- #error "Please fix
" - #endif
-
- unsigned char tos; // 服务类型
- unsigned short tot_len; // 总长度
- unsigned short id; // 标识
- unsigned short frag_off; // 分片偏移
- unsigned char ttl; // 存活时间
- unsigned char protocol; // 协议类型
- unsigned short check; // 校验和
- unsigned int saddr; // 源IP地址
- unsigned int daddr; // 目标IP地址
- // 其他字段...
- };
/usr/include/linux/in.h
- enum {
- IPPROTO_IP = 0, /* Dummy protocol for TCP */
- #define IPPROTO_IP IPPROTO_IP
- IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
- #define IPPROTO_ICMP IPPROTO_ICMP
- IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
- #define IPPROTO_IGMP IPPROTO_IGMP
- IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
- #define IPPROTO_IPIP IPPROTO_IPIP
- IPPROTO_TCP = 6, /* Transmission Control Protocol */
- #define IPPROTO_TCP IPPROTO_TCP
- IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
- #define IPPROTO_EGP IPPROTO_EGP
- IPPROTO_PUP = 12, /* PUP protocol
- ...
- }
如何找到这些结构体
grep -irn "xxx" /usr/inclue/linux