You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Socket技术问题:sendto函数基于IP首部ihl值修改发送数据包及相关疑问

Raw Socket Packet Forging & Sending Issues: Solutions

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_PACKET family and SOCK_RAW type:
    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_ll to specify the network interface and hardware address details, instead of struct 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_len to network byte order
    • Replaces a source IP of 0.0.0.0 with the host's actual IP
  • When ihl is 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 csum function 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.0 if 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:

  1. Loopback Reflection: Many network interfaces reflect outgoing packets back to the local stack for monitoring purposes. If your recv.c is using AF_PACKET with ETH_P_ALL, it will capture both the outgoing packet and the reflected copy. Use tcpdump on your interface to verify if the packet is actually sent twice over the wire, or if it's just a local duplicate.
  2. Accidental Loop in Code: Double-check that your sendto call isn't wrapped in an uncommented loop. Your provided send.c has the while(1) commented out, but if you had it enabled previously, that could explain duplicates.
  3. 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

火山引擎 最新活动