2011-11-27 110 views
6

是否可以在IP協議下使用ICMP套接字?也許是這樣的:ICMP套接字(linux)

socket(PF_INET, <type>, IPPROTO_ICMP)?

我應該把什麼在<類型>字段?我看到了一些使用SOCK_RAW的例子,但是這不會阻止操作系統執行IP協議的工作嗎?

另一件事。操作系統如何知道應該發送ICMP數據報的過程,因爲協議中沒有涉及端口?

回答

7

是的,這是可能的,因爲ping命令執行ICMP。

要找出涉及的系統調用,你可以使用strace那個命令(在root下)。

您還可以查看該命令的源代碼,例如, Debian's ping

還有就是liboping庫,幫助你...

14

的Linux都可以用使用一種特殊的ICMP套接字類型:

socket(PF_INET, SOCK_DGRAM IPPROTO_ICMP); 

這樣您就可以只發送ICMP迴應請求內核將專門處理它(匹配請求/響應,填入校驗和)。

這僅適用於設置了special sysctl的情況。默認情況下,即使root也不能使用這種套接字。您指定可以訪問它的用戶組。以允許根(組0)使用ICMP插座,做到:

sysctl -w net.ipv4.ping_group_range="0 0" 

下面是一個例子程序來演示發送ICMP迴應請求的非常基本的用法:

#include <stdio.h> 
#include <errno.h> 
#include <string.h> 
#include <stdlib.h> 
#include <sys/socket.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <netinet/ip_icmp.h> 
#include <arpa/inet.h> 
#include <sys/select.h> 

//note, to allow root to use icmp sockets, run: 
//sysctl -w net.ipv4.ping_group_range="0 0" 

void ping_it(struct in_addr *dst) 
{ 
    struct icmphdr icmp_hdr; 
    struct sockaddr_in addr; 
    int sequence = 0; 
    int sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP); 
    if (sock < 0) { 
     perror("socket"); 
     return ; 
    } 

    memset(&addr, 0, sizeof addr); 
    addr.sin_family = AF_INET; 
    addr.sin_addr = *dst; 

    memset(&icmp_hdr, 0, sizeof icmp_hdr); 
    icmp_hdr.type = ICMP_ECHO; 
    icmp_hdr.un.echo.id = 1234;//arbitrary id 

    for (;;) { 
     unsigned char data[2048]; 
     int rc; 
     struct timeval timeout = {3, 0}; //wait max 3 seconds for a reply 
     fd_set read_set; 
     socklen_t slen; 
     struct icmphdr rcv_hdr; 

     icmp_hdr.un.echo.sequence = sequence++; 
     memcpy(data, &icmp_hdr, sizeof icmp_hdr); 
     memcpy(data + sizeof icmp_hdr, "hello", 5); //icmp payload 
     rc = sendto(sock, data, sizeof icmp_hdr + 5, 
         0, (struct sockaddr*)&addr, sizeof addr); 
     if (rc <= 0) { 
      perror("Sendto"); 
      break; 
     } 
     puts("Sent ICMP\n"); 

     memset(&read_set, 0, sizeof read_set); 
     FD_SET(sock, &read_set); 

     //wait for a reply with a timeout 
     rc = select(sock + 1, &read_set, NULL, NULL, &timeout); 
     if (rc == 0) { 
      puts("Got no reply\n"); 
      continue; 
     } else if (rc < 0) { 
      perror("Select"); 
      break; 
     } 

     //we don't care about the sender address in this example.. 
     slen = 0; 
     rc = recvfrom(sock, data, sizeof data, 0, NULL, &slen); 
     if (rc <= 0) { 
      perror("recvfrom"); 
      break; 
     } else if (rc < sizeof rcv_hdr) { 
      printf("Error, got short ICMP packet, %d bytes\n", rc); 
      break; 
     } 
     memcpy(&rcv_hdr, data, sizeof rcv_hdr); 
     if (rcv_hdr.type == ICMP_ECHOREPLY) { 
      printf("ICMP Reply, id=0x%x, sequence = 0x%x\n", 
          icmp_hdr.un.echo.id, icmp_hdr.un.echo.sequence); 
     } else { 
      printf("Got ICMP packet with type 0x%x ?!?\n", rcv_hdr.type); 
     } 
    } 
} 

int main(int argc, char *argv[]) 
{ 
    if (argc != 2) { 
     printf("usage: %s destination_ip\n", argv[0]); 
     return 1; 
    } 
    struct in_addr dst; 

    if (inet_aton(argv[1], &dst) == 0) { 

     perror("inet_aton"); 
     printf("%s isn't a valid IP address\n", argv[1]); 
     return 1; 
    } 

    ping_it(&dst); 
    return 0; 
} 

注意,內核如果發送的數據沒有適當的ICMP頭的空間,並且ICMP type必須是8(ICMP_ECHO)並且ICMP代碼必須是0,則將拒絕並且失敗sendto()調用。

+0

您是否創建套接字是錯的? SOCK_DRAM是OSI/ISO 7層堆棧的傳輸層的一部分... ICMP是網絡層中第3層的一部分。我一直在閱讀Stevens的Unix套接字編程,爲了做到這一點,你需要聲明一個SOCK_RAW來獲得icmp包。看看這個代碼[here](http://www.binarytides.com/packet-sniffer-code-c-linux/) –

+1

@Florida_Jake SOCK_DGRAM不綁定到第7層。Unix套接字編程書不描述如何要使用linux特定的ICMP回顯套接字,這裏的例子工作。發送/接收ICMP消息的傳統方式確實是使用SOCK_RAW,並自己構建ICMP消息。我發佈的代碼使用了一個替代的,特定於linux的功能來發送和接收ICMP回顯消息。 – nos

+0

不錯,對我很好,很快就得到了結果。現在是查看IPv6 ping的時候了。 :) –