Socket技术问题:sendto函数基于IP首部ihl值修改发送数据包及相关疑问
Hey there, let's tackle your raw socket problems one by one—these are common gotchas when working with low-level packet manipulation, so I've got you covered.
1. How to Manipulate the Ethernet Header (ethhdr)
When you use socket(AF_INET, SOCK_RAW, IPPROTO_RAW), the kernel handles the Ethernet header automatically, which is why you can't modify it directly. To take full control of the Ethernet frame, you need to use a link-layer raw socket instead of the network-layer one you're currently using.
Here's how to do it:
- Create a socket with
AF_PACKETfamily andSOCK_RAWtype:int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); - Construct your entire frame manually: start with the
struct ethhdr, followed by your IP header and payload. - When sending, use
struct sockaddr_llto specify the network interface and hardware address details, instead ofstruct sockaddr_in.
Example snippet for constructing the Ethernet header:
struct ethhdr *eth = (struct ethhdr *)datagram; // Set destination MAC address (replace with your target's MAC) unsigned char dest_mac[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; memcpy(eth->h_dest, dest_mac, ETH_ALEN); // Set source MAC address (get your interface's MAC via ioctl or ifconfig) unsigned char src_mac[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; memcpy(eth->h_source, src_mac, ETH_ALEN); // Set Ethernet type to IPv4 eth->h_proto = htons(ETH_P_IP);
With this setup, you have full control over every byte of the Ethernet header.
2. Why sendto Modifies IP Header When ihl=5
This behavior is due to how the Linux kernel handles raw sockets with IPPROTO_RAW:
- When
ihl=5(the standard 20-byte IP header), the kernel assumes you're using a "normal" IP header and applies its own processing:- Recalculates the IP checksum (overwriting your manual calculation)
- Converts
tot_lento network byte order - Replaces a source IP of
0.0.0.0with the host's actual IP
- When
ihlis set to any other value, the kernel treats the header as non-standard and skips this automatic processing.
To disable this kernel interference regardless of ihl value, enable the IP_HDRINCL socket option. This tells the kernel that you've fully constructed the IP header and it shouldn't modify anything.
Add this right after creating your socket:
int opt = 1; if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt)) < 0) { perror("Failed to set IP_HDRINCL"); exit(1); }
Once IP_HDRINCL is enabled, you're responsible for:
- Setting the correct checksum (your existing
csumfunction should work) - Converting all multi-byte fields (like
tot_len,id) to network byte order manually - Specifying a valid source IP (or leaving it as
0.0.0.0if you want, but the kernel won't replace it anymore)
3. Why sendto Sends Two Packets (But You Called It Once)
There are a few likely causes here:
- Loopback Reflection: Many network interfaces reflect outgoing packets back to the local stack for monitoring purposes. If your
recv.cis usingAF_PACKETwithETH_P_ALL, it will capture both the outgoing packet and the reflected copy. Usetcpdumpon your interface to verify if the packet is actually sent twice over the wire, or if it's just a local duplicate. - Accidental Loop in Code: Double-check that your
sendtocall isn't wrapped in an uncommented loop. Your providedsend.chas thewhile(1)commented out, but if you had it enabled previously, that could explain duplicates. - ARP Resolution: If the destination IP isn't on your local subnet, the kernel might send an ARP request first—but that's a separate packet, not a duplicate of your forged one.
To test, run tcpdump -i <your-interface> ip host 10.0.2.15 while sending the packet. If you only see one packet in tcpdump, the duplicate is just a local reflection captured by your recv.c script.
Your Provided Code
recv.c
#include<stdio.h> #include<string.h> #include<malloc.h> #include<errno.h> #include<sys/socket.h> #include<sys/types.h> #include<sys/ioctl.h> #include<net/if.h> #include<netinet/in.h> #include<netinet/ip.h> #include<netinet/if_ether.h> #include<netinet/udp.h> #include<linux/if_packet.h> #include<arpa/inet.h> #define min(x,y) (x<y)?x:y void pkt_hex_dump(uint8_t *data, size_t len) { int rowsize = 16; int i, l, linelen, remaining; int li = 0; uint8_t ch; printf("\nPacket hex dump:\n"); printf("Packet Size = %ld\n", len); remaining = len; for (i = 0; i < len; i += rowsize) { printf("%06d\t", li); linelen = min(remaining, rowsize); remaining -= rowsize; for (l = 0; l < linelen; l++) { ch = data[l]; printf("%02X ", (uint32_t) ch); } data += linelen; li += 10; printf("\n"); } } unsigned short csum(unsigned short *ptr,int nbytes) { register long sum; unsigned short oddbyte; register short answer; sum=0; while(nbytes>1) { sum+=*ptr++; nbytes-=2; } if(nbytes==1) { oddbyte=0; *((u_char*)&oddbyte)=*(u_char*)ptr; sum+=oddbyte; } sum = (sum>>16)+(sum & 0xffff); sum = sum + (sum>>16); answer=(short)~sum; return(answer); } int main() { int sock_r = socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL)); if (sock_r == -1) { printf("error in socket"); return -1; } unsigned char* buffer = (unsigned char *)malloc(65536); memset(buffer,0,65536); while(1) { int saddr_len, buflen; struct sockaddr saddr; saddr_len = sizeof saddr; buflen = recvfrom(sock_r, buffer, 65536, 0, &saddr, (socklen_t *)&saddr_len); printf("\n[NEW PACKET]\n"); unsigned char* data = buffer + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr); if(buflen<0) { printf("error in reading recvfrom function\n"); return -1; } union ipv4 { __be32 src; uint8_t ip[4]; }; struct iphdr *iph = (struct iphdr *)(buffer + sizeof(struct ethhdr)); union ipv4 src_ip; src_ip.src = iph->saddr; struct udphdr *udph = (struct udphdr *)(buffer + sizeof(struct ethhdr) + sizeof(struct iphdr)); printf("Source = %d.%d.%d.%d\n",(int)src_ip.ip[0],(int)src_ip.ip[1],(int)src_ip.ip[2],(int)src_ip.ip[3]); printf("Checksum Value : %d\n",(int)iph->check); printf("Checksum UDP : %d\n",(int)udph->check); printf("Data Received:\n%s\n",data); pkt_hex_dump((uint8_t *)iph,buflen - sizeof(struct ethhdr)); //recheck csum values iph->check = 0; udph->source = 0; udph->dest = 0; udph->len = 0; udph->check = 0; printf("\nRechecking Checksum:%4X\n\n",csum((unsigned short *)iph,iph->tot_len)); } close(sock_r); }
send.c (with IP_HDRINCL fix)
#include<stdio.h> //for printf #include<string.h> //memset #include<sys/socket.h> //for socket ofcourse #include<stdlib.h> //for exit(0); #include<errno.h> //For errno - the error number #include<netinet/udp.h> //Provides declarations for udp header #include<netinet/ip.h> //Provides declarations for ip header #define min(x,y) (x<y)?x:y void pkt_hex_dump(uint8_t *data, size_t len) { int rowsize = 16; int i, l, linelen, remaining; int li = 0; uint8_t ch; printf("\nPacket hex dump:\n"); printf("Packet Size = %ld\n", len); remaining = len; for (i = 0; i < len; i += rowsize) { printf("%06d\t", li); linelen = min(remaining, rowsize); remaining -= rowsize; for (l = 0; l < linelen; l++) { ch = data[l]; printf("%02X ", (uint32_t) ch); } data += linelen; li += 10; printf("\n"); } } struct pseudo_header { u_int32_t source_address; u_int32_t dest_address; u_int8_t placeholder; u_int8_t protocol; u_int16_t udp_length; }; unsigned short csum(unsigned short *ptr,int nbytes) { register long sum; unsigned short oddbyte; register short answer; sum=0; while(nbytes>1) { sum+=*ptr++; nbytes-=2; } if(nbytes==1) { oddbyte=0; *((u_char*)&oddbyte)=*(u_char*)ptr; sum+=oddbyte; } sum = (sum>>16)+(sum & 0xffff); sum = sum + (sum>>16); answer=(short)~sum; return(answer); } int main (void) { int s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW); if(s == -1) { perror("Failed to create raw socket"); exit(1); } // Enable IP_HDRINCL to prevent kernel from modifying our IP header int opt = 1; if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt)) < 0) { perror("Failed to set IP_HDRINCL"); exit(1); } struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(80); sin.sin_addr.s_addr = inet_addr ("10.0.2.15"); char *datagram = (char*)malloc(4096); memset (datagram, 0, 4096); struct iphdr *iph = (struct iphdr *)datagram; char* data = datagram + sizeof(struct iphdr) + sizeof(struct udphdr); strcpy(data,"Hello I am here.\n"); iph->ihl = 5; iph->version = 4; iph->tos = 0; // Convert tot_len to network byte order manually iph->tot_len = htons(sizeof (struct iphdr) + sizeof (struct udphdr) + strlen(data)); iph->id = htonl (85492); iph->frag_off = 0; iph->ttl = 255; iph->protocol = IPPROTO_UDP; iph->check = 0; //Set to 0 before calculating checksum //iph->saddr = inet_addr ( "1.2.3.4" ); //Spoof the source ip address iph->daddr = sin.sin_addr.s_addr; iph->check = csum ((unsigned short *) datagram, ntohs(iph->tot_len)); struct udphdr *udph = (struct udphdr *)(datagram + sizeof(struct iphdr)); udph->source = htons (6666); udph->dest = htons (8622); udph->len = htons(8 + strlen(data)); udph->check = 0; //leave checksum 0 now, filled later by pseudo header struct pseudo_header psh; psh.source_address = inet_addr( "1.2.3.4" ); psh.dest_address = sin.sin_addr.s_addr; psh.placeholder = 0; psh.protocol = IPPROTO_UDP; psh.udp_length = htons(sizeof(struct udphdr) + strlen(data) ); int psize = sizeof(struct pseudo_header) + sizeof(struct udphdr) + strlen(data); unsigned char *pseudogram = malloc(psize); memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header)); memcpy(pseudogram + sizeof(struct pseudo_header) , udph , sizeof(struct udphdr) + strlen(data)); udph->check = csum( (unsigned short*) pseudogram , psize); //while (1) { if (sendto (s, datagram, ntohs(iph->tot_len) , 0, (struct sockaddr *) &sin, sizeof (sin)) < 0) { perror("sendto failed"); } else { printf ("Packet Send. Length : %d \n" , ntohs(iph->tot_len)); printf("Checksum Value : %#X\n",(uint16_t)iph->check); printf("Checksum UDP : %#X\n",(uint16_t)udph->check); pkt_hex_dump(datagram,ntohs(iph->tot_len)); } //} close(s); return 0; }
内容的提问来源于stack exchange,提问作者StwayneXG




