Raw Socket UDP客户端发包:tcpdump可见但UDP服务器未接收
Raw Socket构造UDP包无法被SOCK_DGRAM服务器接收排查
我正在学习C语言Raw Socket,编写了基于SOCK_DGRAM的UDP服务器和Raw Socket客户端,目标是手动构造UDP包并通过Raw Socket发送至UDP服务器。客户端和服务器编译运行均无错误或警告,使用netcat -u 172.18.100.181 3333测试时,服务器能正常接收并打印数据包。但使用自制客户端发送数据包时,尽管tcpdump和Wireshark能捕获到相关流量且无错误标记,UDP服务器却未接收到任何数据。
服务器代码(server.c)
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <netdb.h> #define BUFSIZE 1500 int main(void) { int result; struct addrinfo hints; struct addrinfo* server_infos; struct addrinfo* cursor; memset(&hints, 0, sizeof(struct addrinfo)); //setting hints hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; getaddrinfo("172.18.100.181", "3333", &hints, &server_infos); int udpsocket_fd; for(cursor = server_infos; cursor != NULL; cursor = cursor->ai_next) { udpsocket_fd = socket(cursor->ai_family, cursor->ai_socktype, cursor->ai_protocol); if(udpsocket_fd == -1) continue; result = bind(udpsocket_fd, cursor->ai_addr, cursor->ai_addrlen); if(result == 0) break; } if(cursor == NULL) { printf("No suitable interface\n"); exit(1); } while(1) { char buffer[BUFSIZE]; memset(buffer, 0, BUFSIZE); result = recvfrom(udpsocket_fd, buffer, BUFSIZE, 0, NULL, NULL); for(int i = 0; i < BUFSIZE; i++) { if(i % 16 == 0) printf("\n"); printf("%c ", buffer[i]); } } return 0; }
客户端代码(client.c)
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <netdb.h> #include <string.h> #include <stdlib.h> #include <netinet/in.h> #include <net/ethernet.h> #include <unistd.h> #include <netinet/ether.h> #include <netinet/ip.h> #include <netinet/udp.h> #include <errno.h> #include <linux/if_packet.h> #include <netinet/if_ether.h> #define PACKET_BUFFER 1024 #define UDP 17 unsigned short ip_checksum(struct iphdr* ip_header, int ip_header_length) { short* cursor = (short*) ip_header; int checksum = 0; for(int i = 0; i < ip_header_length/2; i++) { checksum += htons(cursor[i]); checksum = (checksum & 0x0000ffff) + (checksum >> 16); } return (unsigned short) (~checksum); } int main(void) { int result; int rawsocket_fd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW); char packet[PACKET_BUFFER]; memset(packet, 0, PACKET_BUFFER); //ethernet header struct ethhdr* ethernet_header = (struct ethhdr*) packet; //source mac ethernet_header->h_source[0] = 0x00; ethernet_header->h_source[1] = 0x15; ethernet_header->h_source[2] = 0x5d; ethernet_header->h_source[3] = 0x6c; ethernet_header->h_source[4] = 0xae; ethernet_header->h_source[5] = 0x2b; //dest mac ethernet_header->h_dest[0] = 0x00; ethernet_header->h_dest[1] = 0x15; ethernet_header->h_dest[2] = 0x5d; ethernet_header->h_dest[3] = 0x6c; ethernet_header->h_dest[4] = 0xae; ethernet_header->h_dest[5] = 0x2b; //protocol ethernet_header->h_proto = htons(ETH_P_IP); //ip header struct iphdr* ip_header = (struct iphdr*) (ethernet_header + 1); ip_header->version = 4; char ip_saddr[INET_ADDRSTRLEN] = "172.18.100.181"; char ip_daddr[INET_ADDRSTRLEN] = "172.18.100.181"; inet_pton(AF_INET, ip_saddr, &(ip_header->saddr)); inet_pton(AF_INET, ip_daddr, &(ip_header->daddr)); ip_header->ihl = 5; ip_header->protocol = UDP; ip_header->ttl = 64; ip_header->tos = 16; ip_header->id = htons(1212); ip_header->check = ip_checksum(ip_header, 20); //udp header struct udphdr* udp_header = (struct udphdr*) (ip_header + 1); udp_header->source = htons(12001); udp_header->dest = htons(3333); udp_header->check = 0; //raw data (payload) char* raw_data = (char*) (udp_header + 1); unsigned short raw_data_length = 0; raw_data[raw_data_length++] = 0x61; raw_data[raw_data_length++] = 0x62; raw_data[raw_data_length++] = 0x63; raw_data[raw_data_length++] = 0x64; raw_data[raw_data_length++] = 0x65; raw_data[raw_data_length++] = 0x66; raw_data[raw_data_length++] = 0x67; raw_data[raw_data_length++] = 0x68; raw_data[raw_data_length++] = 0x69; raw_data[raw_data_length++] = 0x6a; raw_data[raw_data_length++] = 0x6b; raw_data[raw_data_length++] = 0x6c; raw_data[raw_data_length++] = 0x6d; //filling missing fields ip_header->tot_len = htons(raw_data - (char*) ip_header + raw_data_length); udp_header->len = htons(raw_data - (char*) udp_header + raw_data_length); unsigned short packet_total_len = raw_data + raw_data_length - packet; //sockaddr_ll to specify destination mac/interface index/other informations, necessary to use sendto struct sockaddr_ll server_address_ll; //index of interface to send to server_address_ll.sll_ifindex = 2; //length of destination mac address server_address_ll.sll_halen = ETH_ALEN; //Setting destination mac address in server_address_ll.sll_addr[0] = 0x00; server_address_ll.sll_addr[1] = 0x15; server_address_ll.sll_addr[2] = 0x5d; server_address_ll.sll_addr[3] = 0x6c; server_address_ll.sll_addr[4] = 0xae; server_address_ll.sll_addr[5] = 0x2b; result = sendto(rawsocket_fd, packet, packet_total_len, 0, (struct sockaddr*) &server_address_ll, sizeof(struct sockaddr_ll)); if(result == -1) { perror("error"); exit(1); } return 0; }
抓包情况
tcpdump和Wireshark可捕获到客户端发送的流量,且无错误标记,但UDP服务器未接收到数据。
排查与修复建议
1. 修正IP校验和计算顺序
当前代码先计算IP校验和,再设置ip_header->tot_len,但tot_len是IP头部的必填字段,参与校验和计算。正确顺序应为:
// 先填充IP总长度 ip_header->tot_len = htons(raw_data - (char*) ip_header + raw_data_length); // 再计算IP校验和 ip_header->check = ip_checksum(ip_header, 20);
2. 计算UDP校验和
部分Linux内核会丢弃校验和为0的UDP包(除非明确禁用校验和检查)。需要构造UDP伪头部并计算校验和:
// 添加UDP伪头部结构体 struct udp_pseudo_header { uint32_t saddr; uint32_t daddr; uint8_t zero; uint8_t protocol; uint16_t udp_len; } pseudo_hdr; // 填充伪头部 pseudo_hdr.saddr = ip_header->saddr; pseudo_hdr.daddr = ip_header->daddr; pseudo_hdr.zero = 0; pseudo_hdr.protocol = UDP; pseudo_hdr.udp_len = udp_header->len; // 拼接伪头部、UDP头部和payload计算校验和 char checksum_buf[PACKET_BUFFER]; memcpy(checksum_buf, &pseudo_hdr, sizeof(pseudo_hdr)); memcpy(checksum_buf + sizeof(pseudo_hdr), udp_header, ntohs(udp_header->len)); udp_header->check = ip_checksum((struct iphdr*)checksum_buf, sizeof(pseudo_hdr) + ntohs(udp_header->len));
3. 处理回环包接收问题
由于源IP和目的IP均为本机,Raw Socket发送的回环包默认不会被本地SOCK_DGRAM套接字接收。可通过以下方式解决:
- 修改内核参数允许接收本地回环包:
sysctl -w net.ipv4.conf.all.accept_local=1 - 测试时将目的IP改为同一局域网内的其他机器,验证数据包是否能被正常接收
4. 检查IP头部字段完整性
确保IP头部的frag_off字段被正确初始化(设置为0即可,无分片),避免内核因字段异常丢弃数据包。
内容的提问来源于stack exchange,提问作者Fzeh
相关产品推荐
相关产品推荐

