2010-09-17 113 views
1

我試圖發送一個原始數據包使用UDP,以及在我的代碼中構建的IP和UDP標頭。原始數據包成功初始化爲socket(PF_INET, SOCK_RAW, IPPROTO_UDP),套接字選項使用setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(int))設置。在Linux上使用C的原始套接字sendto失敗

問題是當我使用sendto()發送數據包時,出現錯誤「消息太長」。

我的IP頭是20個字節,UDP頭是8個字節,我的數據是12個字節。所以總數只有40個字節。對於單個UDP數據包,這不可能太長。有人可以幫忙嗎?

類型的val是一個指向int

這裏是我的代碼:

#include <unistd.h> 
#include <stdio.h> 
#include <sys/socket.h> 
#include <netinet/ip.h> 
#include <netinet/udp.h> 

#include <string.h> 
#include <stdlib.h> 
#include <arpa/inet.h> 

#include <netinet/in.h> 

//The packet length in byes 
#define PCKT_LEN 50 

//Date size in bytes 
#define DATA_SIZE 12 

//PseudoHeader struct used to calculate UDP checksum. 
typedef struct PseudoHeader{  
    unsigned long int source_ip; 
    unsigned long int dest_ip; 
    unsigned char reserved; 
    unsigned char protocol; 
    unsigned short int udp_length; 
}PseudoHeader; 

// Ripped from Richard Stevans Book 
unsigned short ComputeChecksum(unsigned char *data, int len) 
{ 
    long sum = 0; /* assume 32 bit long, 16 bit short */ 
    unsigned short *temp = (unsigned short *)data; 

    while(len > 1){ 
     sum += *temp++; 
     if(sum & 0x80000000) /* if high order bit set, fold */ 
      sum = (sum & 0xFFFF) + (sum >> 16); 
     len -= 2; 
    } 

    if(len)  /* take care of left over byte */ 
     sum += (unsigned short) *((unsigned char *)temp); 

    while(sum>>16) 
     sum = (sum & 0xFFFF) + (sum >> 16); 

    return ~sum; 
} 

int BindRawSocketToInterface(int rawsock, char *addr, short int port) 
{ 
    struct sockaddr_in s_addr; 
    s_addr.sin_family = AF_INET; 
    s_addr.sin_addr.s_addr = inet_addr(addr); 
    s_addr.sin_port = htons(port); 

    if((bind(rawsock, (struct sockaddr *)&s_addr, sizeof(s_addr)))== -1) 
    { 
     perror("Error binding raw socket to interface\n"); 
     exit(-1); 
    } 

    return 1; 
} 


// Fabricate the IP header or we can use the 
// standard header structures but assign our own values. 
struct ip *CreateIPHeader(char *srcip, char *destip) 
{ 
    struct ip *ip_header; 

    ip_header = (struct ip *)malloc(sizeof(struct ip)); 

    ip_header->ip_v = 4; 
    ip_header->ip_hl = 5; 
    ip_header->ip_tos = 0; 
    ip_header->ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + DATA_SIZE); 
    ip_header->ip_id = htons(111); 
    ip_header->ip_off = 0; 
    ip_header->ip_ttl = 111; 
    ip_header->ip_p = IPPROTO_TCP; 
    ip_header->ip_sum = 0; /* We will calculate the checksum later */ 
    inet_pton(AF_INET, srcip, &ip_header->ip_src); 
    inet_pton(AF_INET, destip, &ip_header->ip_dst); 

    /* Calculate the IP checksum now : 
    The IP Checksum is only over the IP header */ 
    ip_header->ip_sum = ComputeChecksum((unsigned char *)ip_header, ip_header->ip_hl*4); 

    return (ip_header); 
} 

// Creates a the UDP header. 
struct udphdr *CreateUdpHeader(char *srcport, char *destport) 
{ 
    struct udphdr *udp_header; 

    /* Check netinet/udp.h for header definiation */ 

    udp_header = (struct udphdr *)malloc(sizeof(struct udphdr)); 

    udp_header->source = htons(atoi(srcport)); 
    udp_header->dest = htons(atoi(destport)); 
    udp_header->len = htons(sizeof(struct udphdr) + DATA_SIZE); //TODO: need to specify this 
    udp_header->check = htons(0); 

    return (udp_header); 
} 

void CreatePseudoHeaderAndComputeUdpChecksum(struct udphdr *udp_header, struct ip *ip_header, unsigned char *data) 
{ 
    /*The TCP Checksum is calculated over the PseudoHeader + TCP header +Data*/ 

    /* Find the size of the TCP Header + Data */ 
    int segment_len = ntohs(ip_header->ip_len) - ip_header->ip_hl*4; 

    /* Total length over which TCP checksum will be computed */ 
    int header_len = sizeof(PseudoHeader) + segment_len; 

    /* Allocate the memory */ 

    unsigned char *hdr = (unsigned char *)malloc(header_len); 

    /* Fill in the pseudo header first */ 

    PseudoHeader *pseudo_header = (PseudoHeader *)hdr; 

    pseudo_header->source_ip = ip_header->ip_src.s_addr; 
    pseudo_header->dest_ip = ip_header->ip_dst.s_addr; 
    pseudo_header->reserved = 0; 
    pseudo_header->protocol = ip_header->ip_p; 
    pseudo_header->udp_length = htons(segment_len); 


    /* Now copy TCP */ 

    memcpy((hdr + sizeof(PseudoHeader)), (void *)udp_header, 8); 

    /* Now copy the Data */ 

    memcpy((hdr + sizeof(PseudoHeader) + 8), data, DATA_SIZE); 

    /* Calculate the Checksum */ 

    udp_header->check = ComputeChecksum(hdr, header_len); 

    /* Free the PseudoHeader */ 
    free(hdr); 
} 

// Source IP, source port, target IP, target port from the command line arguments 
int main(int argc, char *argv[]) 
{ 
    int sd; 
    char buffer[PCKT_LEN]; 
    char *data = "Hello World!"; 

    // Source and destination addresses: IP and port 
    struct sockaddr_in to_addr; 
    int one = 1; 
    const int *val = &one; 

    printf("IP Header Size: %lu \n", sizeof(struct ip)); 
    printf("UDP Header Size: %lu \n", sizeof(struct udphdr)); 
    printf("Data Size: %d\n", DATA_SIZE); 
    printf("IP Total: %lu \n", sizeof(struct ip) + sizeof(struct udphdr) + DATA_SIZE); 

    memset(buffer, 0, PCKT_LEN); 

    if(argc != 5) 
    { 
     printf("- Invalid parameters!!!\n"); 
     printf("- Usage %s <source hostname/IP> <source port> <target hostname/IP> <target port>\n", argv[0]); 
     exit(-1); 
    } 

    // Create a raw socket with UDP protocol 
    sd = socket(PF_INET, SOCK_RAW, IPPROTO_UDP); 
    if(sd < 0) 
    { 
     perror("socket() error"); 
     exit(-1); 
    } 
    else 
     printf("socket() - Using SOCK_RAW socket and UDP protocol is OK.\n"); 

    //Bind the socket to the source address and port. 
    BindRawSocketToInterface(sd, argv[1], atoi(argv[2])); 

    // Inform the kernel do not fill up the packet structure. we will build our own... 
    if(setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(int)) < 0) 
    { 
     perror("setsockopt() error"); 
     close(sd); 
     exit(-1); 
    } 
    else 
     printf("setsockopt() is OK.\n"); 


    // The source is redundant, may be used later if needed 
    // The address family 
    to_addr.sin_family = AF_INET; 
    to_addr.sin_addr.s_addr = inet_addr(argv[3]); 
    to_addr.sin_port = htons(atoi(argv[4])); 

    //Create the IP header. 
    struct ip *ip_header = CreateIPHeader(argv[1], argv[3]); 
    //Create the UDP header. 
    struct udphdr *udp_header = CreateUdpHeader(argv[2], argv[4]); 
    //Compute UDP checksum 
    CreatePseudoHeaderAndComputeUdpChecksum(udp_header, ip_header, (unsigned char*)data); 

    //Copy IP header, UDP header, and data to the packet buffer. 
    memcpy(buffer, ip_header, sizeof(struct ip)); 
    memcpy(buffer + sizeof(struct ip), udp_header, 8 /*sizeof(struct udphdr)*/); 
    memcpy(buffer + sizeof(struct ip) + 8, data, DATA_SIZE); 

    printf("Using raw socket and UDP protocol\n"); 
    printf("Using Source IP: %s port: %u, Target IP: %s port: %u.\n", argv[1], atoi(argv[2]), argv[3], atoi(argv[4])); 


    if(sendto(sd, buffer, htons(20)/*ip_header->ip_len*/, 0, (struct sockaddr *)&to_addr, sizeof(to_addr)) < 0) 
    { 
     perror("sendto() error"); 
    } 
    else 
    { 
     printf("sendto() is OK.\n"); 
    } 

    free(ip_header); 
    free(udp_header); 
    close(sd); 
    return 0; 
} 
+1

顯示這樣做的代碼(並且在你的setsockopt中,val的類型是什麼? – nos 2010-09-17 17:44:09

回答

3
if(sendto(sd, buffer, htons(20)/*ip_header->ip_len*/, 0, (struct sockaddr *)&to_addr, sizeof(to_addr)) < 0) 

這裏的長度是錯誤的,你不應該指定網絡字節順序。

一個簡單的20就足夠了。 htons(20)將是一個很小的endian機器上的一個非常大的數字。 (如果你想發送其他東西,只是IP頭,你應該在長度中包含這個,聽起來像你的緩衝區是40字節,而不是20)

+0

nos,謝謝你的解決方案。sendto現在可以返回,我相信現在通過網絡發送數據包。現在遇到另一個問題,即在目標主機(我的服務器)沒有收到數據包。你認爲它可能與我的UDP數據報格式有什麼不對嗎? – Jeff 2010-09-20 15:15:12

+0

使用wireshark進行驗證,通常很好地指出數據包中的錯誤。校驗和可能是一個罪魁禍首,確保它是正確的。你正在設置IPPROTO_TCP,不應該是IPPROTO_UDP? – nos 2010-09-20 16:07:35

+0

我通過Wireshark檢查數據包時,我想到了將IPPROTO_TCP更改爲IPPROTO_UDP和數據包。 ,但你只是爲我節省了幾個小時的工作,謝謝你的幫助! – Jeff 2010-09-20 16:33:34

0

你可以使用我自己的函數,噸有你的問題:

#define NDEBUG 
#define COUNTMAX 3000 
#define MAX_FAKE 100 
#define ERROR -1 
#define TYPE_A 1 
#define TYPE_PTR 12 
#define CLASS_INET 1 
#define ERROR -1 

#ifndef IPVERSION 
    #define IPVERSION 4 
#endif     /* IPVERISON */ 

#ifndef IP_MAXPACKET 
    #define IP_MAXPACKET 65535 
#endif     /* IP_MAXPACKET */ 

#define DNSHDRSIZE 12 

#ifndef IP_MAXPACKET 
#define IP_MAXPACKET 65535 
#endif     /* IP_MAXPACKET */ 

#define IPHDRSIZE  sizeof(struct iphdr) 
#define UDPHDRSIZE sizeof(struct udphdr) 
    int udp_send(int s, unsigned long saddr, unsigned long daddr,unsigned short sport,unsigned short dport,char *datagram, unsigned datasize) 
{ 
    struct sockaddr_in sin; 
    struct iphdr *ip; 
    struct udphdr *udp; 
    unsigned char *data; 
    unsigned char packet[4024]; 


    ip  = (struct iphdr  *)packet; 
    udp = (struct udphdr *)(packet+IPHDRSIZE); 
    data = (unsigned char *)(packet+IPHDRSIZE+UDPHDRSIZE); 

    memset(packet,0,sizeof(packet)); 

    udp->source = htons(sport); 
    udp->dest = htons(dport); 
    udp->len  = htons(UDPHDRSIZE+datasize); 
    udp->check = 0;   

    memcpy(data,datagram,datasize); 

    ip->saddr.s_addr = saddr; 
    ip->daddr.s_addr = daddr; 
    ip->version = 4;    /*ip version*/ 
    ip->ihl  = 5; 
    ip->ttl  = 245; 
    ip->id  = random()%5985; 
    ip->protocol = IPPROTO_UDP; /*protocol type*/ 
    ip->tot_len = htons((IPHDRSIZE + UDPHDRSIZE + datasize)); 
    ip->check = 0; 
    ip->check = in_cksum((char *)packet,IPHDRSIZE); 

    sin.sin_family=AF_INET; 
    sin.sin_addr.s_addr=daddr; 
    sin.sin_port=udp->dest; 
     printf ("socket: %d, packet: %s,size: %d, struct addr: %p, size: %i", s, packet, IPHDRSIZE+UDPHDRSIZE+datasize, (struct sockaddr*)&sin, sizeof(struct sockaddr)); 
    return sendto(s, packet, IPHDRSIZE+UDPHDRSIZE+datasize, 0, (struct sockaddr*)&sin, sizeof(struct sockaddr)); 
}// end of udp_send function 
0

到的sendto()的調用並不顯得如右圖所示,按照意見,甚至更正:
原文:SENDTO(SD,緩衝,htons(20)/ ip_header-> ip_len /,...); Orr as corrected:sendto(sd,buffer,20/ip_header-> ip_len /,...); 數據長度參數應該是頭和數據長度的總和:20 + 8 + 12 = 40。一般的解決方案可以是: sendto(sd,buffer,sizeof(struct ip)+ sizeof(struct udphdr)+ DATA_SIZE, ...); 有了這個改變,它開始爲我工作。

相關問題