<解釋原理>
我們要自行建立一個 "ICMP Header" 並送到對方主機
倘若對方主機活著且存在,他將會 REPLY "IP Header" 和 "ICMP Header" 回來
所以我們要使用
socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) 來建立我們的 socket
註:這只是個簡單的範例,其中還有許多BUG需要深入探討的哦,但在這裡先不多談
<程式碼>
/* raw_ping.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <arpa/inet.h> /* 做 checksum 運算 * 可以驗證資料在傳輸過程中有無損壞 */ unsigned short checksum(unsigned short *buf, int bufsz){ unsigned long sum = 0xffff; while(bufsz > 1){ sum += *buf; buf++; bufsz -= 2; } /* 因為傳進來的時候是 unsigned short * 所以這裡記得轉成 unsigned char */ if(bufsz == 1) sum += *(unsigned char*)buf; sum = (sum & 0xffff) + (sum >> 16); sum = (sum & 0xffff) + (sum >> 16); return ~sum; } int main(int argc, char *argv[]){ int sockfd; /* 此結構已經定義在 netinet/ip_icmp.h 裡面 * 想知道完整的結構體可以去 google */ struct icmphdr hdr; struct sockaddr_in addr; int n; char buf[2000]; struct icmphdr *icmphdrptr; struct iphdr *iphdrptr; if(argc != 2){ printf("usage : %s IPADDR\n", argv[0]); return 1; } addr.sin_family = PF_INET; //IPv4 //inet_pton() 可以把 IP 位址轉成 network order 的形式 n = inet_pton(PF_INET, argv[1], &addr.sin_addr); if(n<0){ perror("inet_pton"); return -1; } /*建立 RAW socket*/ sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP); if(sockfd<0){ perror("socket"); return -1; } //先清空 hdr (但這裡不清也無所謂) memset(&hdr, 0, sizeof(hdr)); /*先設定 ICMP Header*/ hdr.type = ICMP_ECHO; hdr.code = 0; hdr.checksum = 0; hdr.un.echo.id = 0; hdr.un.echo.sequence = 0; /*計算 ICMP Header 的 checksum*/ hdr.checksum = checksum((unsigned short *)&hdr, sizeof(hdr)); /*傳送只有 ICMP Header 的 ICMP 封包*/ n = sendto(sockfd, (char*)&hdr, sizeof(hdr), 0, (struct sockaddr *)&addr, sizeof(addr)); if(n<1){ perror("sendto"); return -1; } /* 送完 ICMP 封包後 * 接著當然要來接收 ICMP ECHO REPLY 囉 */ /*清空 buffer (其實這邊不清也無所謂)*/ memset(buf, 0, sizeof(buf)); /*接收來自對方主機的 ICMP ECHO REPLY*/ n = recv(sockfd, buf, sizeof(buf), 0); if(n<1){ perror("recv"); return -1; } /*從接收到的資料中取出 IP Header 的部分*/ iphdrptr = (struct iphdr*)buf; /*從接收到的資料中取出 ICMP Header 的部分*/ icmphdrptr = (struct icmphdr*)(buf + (iphdrptr->ihl)*4); /*選出 ICMP Header 的類型*/ if(icmphdrptr->type != 0){ //printf("checksum = %d\n", hdr.checksum); //如果 type 是3的話就多印出他的 code ,以便知道發生甚麼問題 if(icmphdrptr->type == 3){ printf("received ICMP %d, and the code is %d\n", icmphdrptr->type, icmphdrptr->code); } else{ printf("received ICMP %d\n", icmphdrptr->type); printf("The host %s is alive\n", argv[1]); } } close(sockfd); return 0; }
註:開啟 RAW socket 需要 root 權限,記得用 sudo 執行程式
<執行圖>
用 sudo ./raw_ping (+你想ping的IP)
由上圖可看到 ping 自己時收到 ICMP type 8,8號的意思是"ICMP ECHO"
表示你 ping 的主機是活著且存在的
想知道更多的 ICMP Type 可以去查閱 維基百科-控制消息協議
沒有留言:
張貼留言