2012-03-13 60 views
4

我試圖創建C中的ICMP ping測試計劃,但時遇到的成功發送數據包的困難。 sendto函數返回字節數和所有內容,但實際上並未發送數據包。我已經在目標計算機上用WireShark驗證了這一點。在主機上的常規ping工作正常,並在WireShark中顯示。ICMP報文沒有被髮送ç

這裏是我的代碼:

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netdb.h> 
#include <netinet/in.h> 
#include <netinet/in_systm.h> 
#include <netinet/ip.h> 
#include <netinet/ip_icmp.h> 
#include <string.h> 
#include <arpa/inet.h> 
#include <sys/select.h> 
#include <sys/time.h> 

unsigned short cksum(unsigned short *addr, int len); 

int main(int argc, char *argv[]) 
{ 
    int sock; 
    char send_buf[400], recv_buf[400], src_name[256], src_ip[15], dst_ip[15]; 
    struct ip *ip = (struct ip *)send_buf; 
    struct icmp *icmp = (struct icmp *)(ip + 1); 
    struct hostent *src_hp, *dst_hp; 
    struct sockaddr_in src, dst; 
    struct timeval t; 
    int on; 
    int num = 10; 
    int failed_count = 0; 
    int bytes_sent, bytes_recv; 
    int dst_addr_len; 
    int result; 
    fd_set socks; 

    /* Initialize variables */ 
    on = 1; 
    memset(send_buf, 0, sizeof(send_buf)); 

    /* Check for valid args */ 
    if(argc < 2) 
    { 
     printf("\nUsage: %s <dst_server>\n", argv[0]); 
     printf("- dst_server is the target\n"); 
     exit(EXIT_FAILURE); 
    } 

    /* Check for root access */ 
    if (getuid() != 0) 
    { 
     fprintf(stderr, "%s: This program requires root privileges!\n", argv[0]); 
     exit(EXIT_FAILURE); 
    } 

    /* Get source IP address */ 
    if(gethostname(src_name, sizeof(src_name)) < 0) 
    { 
     perror("gethostname() error"); 
     exit(EXIT_FAILURE); 
    } 
    else 
    { 
     if((src_hp = gethostbyname(src_name)) == NULL) 
     { 
      fprintf(stderr, "%s: Can't resolve, unknown source.\n", src_name); 
      exit(EXIT_FAILURE); 
     } 
     else 
      ip->ip_src = (*(struct in_addr *)src_hp->h_addr); 
    } 

    /* Get destination IP address */ 
    if((dst_hp = gethostbyname(argv[1])) == NULL) 
    { 
     if((ip->ip_dst.s_addr = inet_addr(argv[1])) == -1) 
     { 
      fprintf(stderr, "%s: Can't resolve, unknown destination.\n", argv[1]); 
      exit(EXIT_FAILURE); 
     } 
    } 
    else 
    { 
     ip->ip_dst = (*(struct in_addr *)dst_hp->h_addr); 
     dst.sin_addr = (*(struct in_addr *)dst_hp->h_addr); 
    } 

    sprintf(src_ip, "%s", inet_ntoa(ip->ip_src)); 
    sprintf(dst_ip, "%s", inet_ntoa(ip->ip_dst)); 
    printf("Source IP: '%s' -- Destination IP: '%s'\n", src_ip, dst_ip); 

    /* Create RAW socket */ 
    if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) 
    { 
     perror("socket() error"); 

     /* If something wrong, just exit */ 
     exit(EXIT_FAILURE); 
    } 

    /* Socket options, tell the kernel we provide the IP structure */ 
    if(setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) 
    { 
     perror("setsockopt() for IP_HDRINCL error"); 
     exit(EXIT_FAILURE); 
    } 

    /* IP structure, check the ip.h */ 
    ip->ip_v = 4; 
    ip->ip_hl = 5; 
    ip->ip_tos = 0; 
    ip->ip_len = htons(sizeof(send_buf)); 
    ip->ip_id = htons(321); 
    ip->ip_off = htons(0); 
    ip->ip_ttl = 255; 
    ip->ip_p = IPPROTO_ICMP; 
    ip->ip_sum = 0; 

    /* ICMP structure, check ip_icmp.h */ 
    icmp->icmp_type = ICMP_ECHO; 
    icmp->icmp_code = 0; 
    icmp->icmp_id = 123; 
    icmp->icmp_seq = 0; 

    /* Set up destination address family */ 
    dst.sin_family = AF_INET; 

    /* Loop based on the packet number */ 
    for(int i = 1; i <= num; i++) 
    { 
     /* Header checksums */ 
     icmp->icmp_cksum = 0; 
     ip->ip_sum = cksum((unsigned short *)send_buf, ip->ip_hl); 
     icmp->icmp_cksum = cksum((unsigned short *)icmp, sizeof(send_buf) - sizeof(struct icmp)); 

     /* Get destination address length */ 
     dst_addr_len = sizeof(dst); 

     /* Set listening timeout */ 
     t.tv_sec = 5; 
     t.tv_usec = 0; 

     /* Set socket listening descriptors */ 
     FD_ZERO(&socks); 
     FD_SET(sock, &socks); 

     /* Send packet */ 
     if((bytes_sent = sendto(sock, send_buf, sizeof(send_buf), 0, (struct sockaddr *)&dst, dst_addr_len)) < 0) 
     { 
      perror("sendto() error"); 
      failed_count++; 
      printf("Failed to send packet.\n"); 
      fflush(stdout); 
     } 
     else 
     { 
      printf("Sent %d byte packet... ", bytes_sent); 

      fflush(stdout); 

      /* Listen for the response or timeout */ 
      if((result = select(sock + 1, &socks, NULL, NULL, &t)) < 0) 
      { 
       perror("select() error"); 
       failed_count++; 
       printf("Error receiving packet!\n"); 
      } 
      else if (result > 0) 
      { 
       printf("Waiting for packet... "); 
       fflush(stdout); 

       if((bytes_recv = recvfrom(sock, recv_buf, sizeof(ip) + sizeof(icmp) + sizeof(recv_buf), 0, (struct sockaddr *)&dst, (socklen_t *)&dst_addr_len)) < 0) 
       { 
        perror("recvfrom() error"); 
        failed_count++; 
        fflush(stdout); 
       } 
       else 
        printf("Received %d byte packet!\n", bytes_recv); 
      } 
      else 
      { 
       printf("Failed to receive packet!\n"); 
       failed_count++; 
      } 

      fflush(stdout); 

      icmp->icmp_seq++; 
     } 
    } 

    /* Display success rate */ 
    printf("Ping test completed with %d%% success rate.\n", (((num - failed_count)/num) * 100)); 

    /* close socket */ 
    close(sock); 

    return 0; 
} 

/* One's Complement checksum algorithm */ 
unsigned short cksum(unsigned short *addr, int len) 
{ 
    int nleft = len; 
    int sum = 0; 
    unsigned short *w = addr; 
    unsigned short answer = 0; 

    while (nleft > 1) 
    { 
     sum += *w++; 
     nleft -= 2; 
    } 

    if (nleft == 1) 
    { 
     *(unsigned char *)(&answer) = *(unsigned char *)w; 
     sum += answer; 
    } 

    sum = (sum >> 16) + (sum & 0xffff); 
    sum += (sum >> 16); 
    answer = ~sum; 

    return (answer); 
} 

在我做錯了什麼有什麼想法?另外,校驗和好嗎?對兩者使用0都可以嗎?

編輯:好吧,所以我設法得到正確的數據包感謝下面的人!目標計算機將發送一個迴應回覆。但是,現在,我的程序沒有收到答覆。 select()函數總是超時。我已經更新了我的代碼。

編輯2:好吧,我得到它的工作!我需要將套接字協議設置爲IPPROTO_ICMP而不是IPPROTO_RAW並且它工作得很好!再次感謝你們評論!真的幫了忙!看起來我只能標記一個正確的答案,但你們都幫助解決這個問題。 :)

回答

2

只有一兩件事我注意到...

你有這樣的:

struct ip *ip = (struct ip *)send_buf; 

然後,要指定目標字段:

ip->ip_dst = (*(struct in_addr *)dst_hp->h_addr) 

而且然後你用memset的擦除它(因爲send_buff指向同一件事):

memset(send_buf, 0, sizeof(send_buf)); 

所以,當你試圖讓ip_dst這裏:

dst.sin_addr = ip->ip_dst; 

你得到0,而不是你先前設置。

+0

謝謝,我完全忽略了!我將memset調用移至頂端,現在我可以看到來自WireShark的數據包。它作爲ping迴應請求進入,但沒有回覆被髮送回主機,並且選擇呼叫超時。任何線索爲什麼? – Zac 2012-03-13 19:14:02

1

乍一看:你不能靠時間結構後選擇()。 你也應該設置usec。

所以在代碼中,包括叔值內的for循環規格:

for (i = 1; i <= num; i++) { 
    t.tv_sec = 5; 
    t.tv_usec = 0; 
    ... 

否則,當你得到第二次迭代噸(可以)改變。

在你的sprintf(src_ip,...)和dst_ip你省略格式。

1

除了ebutusov的答覆:

ip->ip_sum = 0; 
icmp->icmp_cksum = htons(~(ICMP_ECHO << 8)); 

都是不正確。
您需要正確計算校驗和(對於兩者都是相同的算法,但涵蓋了不同的字段)。

+0

是的,計算校驗和正確解決了問題!回顯應答被髮回。但即使回覆,選擇功能也會超時。該設置有問題... – Zac 2012-03-13 21:14:50

+0

您沒有綁定您的套接字。所以操作系統沒有理由將ICMP回覆傳遞給你。在原始套接字中,它不會查看發送的內容,因此無法知道接收到的ICMP是對它的回覆。 – ugoren 2012-03-14 08:14:12