ICMP協(xié)議是做什么的
ICMP是"Internet Control Message Protocol"(Internet控制消息協(xié)議)的縮寫。它是TCP/IP協(xié)議族的一個(gè)子協(xié)議,用于在IP主機(jī)、路由器之間傳遞控制消息。
控制消息是指網(wǎng)絡(luò)通不通、主機(jī)是否可達(dá)、路由是否可用等網(wǎng)絡(luò)本身的消息。這些控制消息雖然并不傳輸用戶數(shù)據(jù),但是對于用戶數(shù)據(jù)的傳遞起著重要的作用。
ICMP協(xié)議是一種面向無連接的協(xié)議,用于傳輸出錯(cuò)報(bào)告控制信息。它是一個(gè)非常重要的協(xié)議,它對于網(wǎng)絡(luò)安全具有極其重要的意義。
主要作用:主機(jī)探測、路由維護(hù)、路由選擇、流量控制。我們常用的ping命令和traceroute命令都是通過ICMP協(xié)議來實(shí)現(xiàn)的。
ICMP使用的是什么端口(一個(gè)面試陷阱)?
Linux下端口的劃分和使用是由IANA(Internet Assigned Numbers Authority,因特網(wǎng)已分配數(shù)值權(quán)威機(jī)構(gòu))維護(hù)的,端口號(hào)被劃分為3個(gè)段:
0~1023,這些端口由IANA分配和控制,可能的話,相同端口號(hào)就分配給TCP、UDP和SCTP的同一給定服務(wù)。如80端口被賦予web服務(wù)
102449151,這些端口不受IANA控制,不過由IANA登記并提供他們的使用情況清單,已方便整個(gè)群體。相同端口號(hào)也分配給TCP和UDP的同一給定服務(wù)。如60006003端口分配給這兩種協(xié)議的X Window服務(wù)器。
49152~65535,動(dòng)態(tài)端口。IANA不管這些端口,就是我們所說的臨時(shí)端口。(49152這個(gè)魔數(shù)是65536的四分之三)。
IANA端口號(hào)查詢地址如下:
http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
常用端口號(hào):
| 服務(wù)名 | 端口號(hào) |
|---|---|
| FTP | 21 |
| SSH (Secure Shell) | 22 |
| TELNET | 23 |
| MAIL(smtp\pop3) | 25\110 |
| DNS | 53 |
| DHCP | 67 |
| HTTP | 80 |
| windows遠(yuǎn)程終端 | 3389 |
| tomcat | 8080 |
| mysql | 3306 |
| ORACLE | 1521、1526 |
ETH,IP,TCP層的報(bào)文格式
我們先來回顧一下ETH,IP,TCP層的報(bào)文格式
ETH層記錄了MAC地址,IP層記錄了IP地址,TCP層記錄了PORT。這樣TCP協(xié)議就可以通過Socket(IP:PORT)來識(shí)別對方了。
/*
* 1. eth level[14Bytes]
* EthHeader{dst:6, src:6, type:2}
* EthData(RFC894)
* ----------------------------------------------------------------------
* | 6Bytes | 6Bytes | 2Bytes | 46~1500Bytes | 4Bytes |
* ----------------------------------------------------------------------
* | DstMac | SrcMac | Type | Data | CRC | : ETH
* | 0806 | ARP(28) | PAD(18) |
* | 0835 | PARP(28) | PAD(18) |
* | 0800 | IP(4~1500) |
* | IPHeader | IPData | : IP
* | TCPHeader | TCPData | : TCP
* | APPData | : APP
*
* 2. IP level[20Bytes]
* IPPacket:{IPHeader(20) + TCPHeader(20+) + [TCPData]}
* ====================================================
* 0---------8---------15-------23--------31
* | v_hl(1) | tos(1) | len(2) |
* | id(2) | offset(2) |
* | ttl(1) | proto(1) | checksum(2) |
* | src(4) |
* | dst(4) |
* $ option $
* $ data $
* ====================================================
* IPHeader(20){v_hl:1, tos:1, len:2, id:2, offset:2, ttl:1, proto:1, checksum:2, src:4, dst:4}
*
* 3. TCP level[20+Bytes]
* ====================================================
* 0---------8---------15-------23--------31
* | src port(2) | dst port(2) |
* | seq(4) |
* | ack(4) |
* |hdlen(1) |flags(1) | window size(2) |
* | checksum(2) | urgent pointer(2) |
* $ option $
* $ data $
* TCPHeader{srcport:2, dstport:2, seq:4, ack:4, hdr_len:1, flags:1, winsize:2, checksum:2, urgent:2}
*/
ICMP報(bào)文格式
詳細(xì)信息:https://www.rfc-editor.org/info/rfc792
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unused |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Internet Header + 64 bits of Original Data Datagram |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct icmphdr
{
u_int8_t type; /* message type */
u_int8_t code; /* type sub-code */
u_int16_t checksum;
union
{
struct
{
u_int16_t id;
u_int16_t sequence;
} echo; /* echo datagram */
u_int32_t gateway; /* gateway address */
struct
{
u_int16_t __unused;
u_int16_t mtu;
} frag; /* path mtu discovery */
} un;
};
type:一個(gè) 8 位類型字段,表示 ICMP 數(shù)據(jù)包類型;
code:一個(gè) 8 位代碼域,表示指定類型中的一個(gè)功能,如果一個(gè)類型中只有一種功能,代碼域置為 0;
checksum:數(shù)據(jù)包中 ICMP 部分上的一個(gè) 16 位檢驗(yàn)和;
ICMP報(bào)文中沒有PORT,其是根據(jù)id+sequence來進(jìn)行目標(biāo)判斷的。
常見的ICMP報(bào)文類型
ICMP提供多種類型的消息為源端節(jié)點(diǎn)提供網(wǎng)絡(luò)層的故障信息反饋,它的報(bào)文類型可以歸納為以下5個(gè)大類:
診斷報(bào)文(類型8,代碼0;類型0,代碼0);
目的不可達(dá)報(bào)文(類型3,代碼0-15);
重定向報(bào)文(類型5,代碼0-4);
超時(shí)報(bào)文(類型11,代碼0-1);
信息報(bào)文(類型12-18)。
回音(Echo)屬于資訊信息。ping命令就是利用了該類型的ICMP包。當(dāng)使用ping命令的時(shí)候,將向目標(biāo)主機(jī)發(fā)送Echo-詢問類型的ICMP包,而目標(biāo)主機(jī)在接收到該ICMP包之后,會(huì)回復(fù)Echo-回答類型的ICMP包,并將詢問ICMP包包含在數(shù)據(jù)部分。ping命令是我們進(jìn)行網(wǎng)絡(luò)排查的一個(gè)重要工具。例如一個(gè)IP地址可以通過ping命令收到回復(fù),那么其他的網(wǎng)絡(luò)協(xié)議通信方式也很有可能成功。
源頭冷卻(source quench)屬于錯(cuò)誤消息。如果某個(gè)主機(jī)快速的向目的傳送數(shù)據(jù),而目的地主機(jī)沒有匹配的處理能力,目的主機(jī)可以向出發(fā)主機(jī)發(fā)出給類型的ICMP包,提醒出發(fā)主機(jī)放慢發(fā)送速度。
目的地?zé)o法到達(dá)(Destination Uncreachale)屬于錯(cuò)誤信息。如果一個(gè)路由器接收到一個(gè)沒辦法進(jìn)一步接力的IP包,它會(huì)想出發(fā)主機(jī)發(fā)送該類型的ICMP包。比如當(dāng)IP包到達(dá)最后一個(gè)路由器,路由器發(fā)現(xiàn)目的地主機(jī)down機(jī),就會(huì)向出發(fā)主機(jī)發(fā)送目的地?zé)o法到達(dá)(Destinaton Unreacherable)類型的ICMP包。目的地?zé)o法到達(dá)還有其他可能的原因,比如不存在接力路徑,比如不被接收的端口號(hào)等等。
超時(shí)(Time Exceded)屬于錯(cuò)誤信息。IPV4中的Time to Live(TTL)和IPV6中的Hop Limit會(huì)隨著經(jīng)過的路由器而遞減,當(dāng)這個(gè)區(qū)域值減為0時(shí),就認(rèn)為該IP包超時(shí)(Time Exceeded)。Time Exceeded就是TTL減為0時(shí)路由器發(fā)給出發(fā)地主機(jī)的ICMP包,通知它發(fā)生了超時(shí)錯(cuò)誤。 traceroute就利用了這種類型的ICMP包。traceroute命令用來發(fā)現(xiàn)IP接力路徑上的各個(gè)路由器。它向目的地發(fā)送IP包,第一次的時(shí)候,將TTL設(shè)為1,引發(fā)第一個(gè)路由器的Time Exceeded錯(cuò)誤。這樣,第一個(gè)路由器恢復(fù)ICMP包,從而讓出發(fā)主機(jī)知道途徑的第一個(gè)路由器的信息。隨后TTL被設(shè)置為2,,3,4。。。直到到達(dá)目的主機(jī)。這樣,沿途的每個(gè)路由器都會(huì)向出發(fā)主機(jī)發(fā)送ICMP包來匯報(bào)錯(cuò)誤。traceroute將ICMP包的信息打印在屏幕上,就是接力路徑的信息了。
重新定向(redirect)屬于錯(cuò)誤信息。當(dāng)一個(gè)路由器收到一個(gè)IP包,對照其routing table,發(fā)現(xiàn)自己不應(yīng)該受到該IP包,它會(huì)向出發(fā)主機(jī)發(fā)送重新定向類型的ICMP,提醒出發(fā)主機(jī)修改自己的routing table。比如下面的網(wǎng)絡(luò):
參見:https://www.cnblogs.com/jingmoxukong/p/3811262.html
這是一個(gè)完整的ICMP類型的列表:
| TYPE | CODE | Description |
|---|---|---|
| 0 | 0 | Echo Reply--回顯應(yīng)答(Ping應(yīng)答) |
| 3 | 0 | Network Unreachable--網(wǎng)絡(luò)不可達(dá) |
| 3 | 1 | Host Unreachable--主機(jī)不可達(dá) |
| 3 | 2 | Protocol Unreachable--協(xié)議不可達(dá) |
| 3 | 3 | Port Unreachable--端口不可達(dá) |
| 3 | 4 | Fragmentation needed but no frag. bit set--需要進(jìn)行分片但設(shè)置不分片比特 |
| 3 | 5 | Source routing failed--源站選路失敗 |
| 3 | 6 | Destination network unknown--目的網(wǎng)絡(luò)未知 |
| 3 | 7 | Destination host unknown--目的主機(jī)未知 |
| 3 | 8 | Source host isolated (obsolete)--源主機(jī)被隔離(作廢不用) |
| 3 | 9 | Destination network administratively prohibited--目的網(wǎng)絡(luò)被強(qiáng)制禁止 |
| 3 | 10 | Destination host administratively prohibited--目的主機(jī)被強(qiáng)制禁止 |
| 3 | 11 | Network unreachable for TOS--由于服務(wù)類型TOS,網(wǎng)絡(luò)不可達(dá) |
| 3 | 12 | Host unreachable for TOS--由于服務(wù)類型TOS,主機(jī)不可達(dá) |
| 3 | 13 | Communication administratively prohibited by filtering--由于過濾,通信被強(qiáng)制禁止 |
| 3 | 14 | Host precedence violation--主機(jī)越權(quán) |
| 3 | 15 | Precedence cutoff in effect--優(yōu)先中止生效 |
| 4 | 0 | Source quench--源端被關(guān)閉(基本流控制) |
| 5 | 0 | Redirect for network--對網(wǎng)絡(luò)重定向 |
| 5 | 1 | Redirect for host--對主機(jī)重定向 |
| 5 | 2 | Redirect for TOS and network--對服務(wù)類型和網(wǎng)絡(luò)重定向 |
| 5 | 3 | Redirect for TOS and host--對服務(wù)類型和主機(jī)重定向 |
| 8 | 0 | Echo request--回顯請求(Ping請求) |
| 9 | 0 | Router advertisement--路由器通告 |
| 10 | 0 | Route solicitation--路由器請求 |
| 11 | 0 | TTL equals 0 during transit--傳輸期間生存時(shí)間為0 |
| 11 | 1 | TTL equals 0 during reassembly--在數(shù)據(jù)報(bào)組裝期間生存時(shí)間為0 |
| 12 | 0 | IP header bad (catchall error)--壞的IP首部(包括各種差錯(cuò)) |
| 12 | 1 | Required options missing--缺少必需的選項(xiàng) |
| 13 | 0 | Timestamp request (obsolete)--時(shí)間戳請求(作廢不用) |
| 14 | 0 | Timestamp reply (obsolete)--時(shí)間戳應(yīng)答(作廢不用) |
| 15 | 0 | Information request (obsolete)--信息請求(作廢不用) |
| 16 | 0 | Information reply (obsolete)--信息應(yīng)答(作廢不用) |
| 17 | 0 | Address mask request--地址掩碼請求 |
| 18 | 0 | Address mask reply--地址掩碼應(yīng)答 |
如何判斷目標(biāo)主機(jī)是否可達(dá)
源主機(jī)向目標(biāo)主機(jī)發(fā)送ICMP回顯請求數(shù)據(jù)包,然后等待目標(biāo)主機(jī)的回答。
目標(biāo)主機(jī)在收到一個(gè)ICMP回顯請求數(shù)據(jù)包后,它會(huì)交換源、目的主機(jī)的地址,然后將收到的ICMP回顯請求數(shù)據(jù)包中的數(shù)據(jù)部分原封不動(dòng)地封裝在自己的ICMP回顯應(yīng)答數(shù)據(jù)包中,然后發(fā)回給發(fā)送ICMP回顯請求的一方。
源主機(jī)收到回顯應(yīng)答報(bào)文核對id和seq后,便認(rèn)為目標(biāo)主機(jī)的回顯服務(wù)正常,也即物理連接暢通。
簡單來說:向目標(biāo)主機(jī)發(fā)送ECHO報(bào)文,接收ECHO_REPLY報(bào)文,則認(rèn)為目標(biāo)主機(jī)可達(dá)。
需要注意
ICMP協(xié)議是IP協(xié)議的排錯(cuò)助手,它可以幫助人們及時(shí)發(fā)現(xiàn)IP通信中出現(xiàn)的故障?;贗CMP的ping和traceroute也構(gòu)成了重要的網(wǎng)絡(luò)診斷工具。
然而需要注意的是,盡管ICMP的設(shè)計(jì)是出于好的意圖,但I(xiàn)CMP卻經(jīng)常被黑客借用進(jìn)行網(wǎng)絡(luò)攻擊,比如利用偽造的IP包引發(fā)大量的ICMP回復(fù),并將這些ICMP包導(dǎo)向受害主機(jī),從而形成DoS攻擊。
而redirect類型的ICMP包可以引起某個(gè)主機(jī)更改自己的routing table,所以也被用做攻擊工具。許多站點(diǎn)選擇忽視某些類型的ICMP包來提供自身的安全性。
C語言使用ICMP報(bào)文實(shí)現(xiàn)ping命令
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/ip_icmp.h>
#define ICMP_PACKET_SIZE 16
#define TIMEOUT_SECONDS 2
uint16_t ident=0;
const char *ping_desc(uint8_t type, uint8_t code)
{
switch(type)
{
case ICMP_ECHOREPLY:
return "Echo Reply";
case ICMP_ECHO:
return "Echo Request";
case ICMP_PARAMPROB:
return "Bad Parameter";
case ICMP_SOURCEQUENCH:
return "Packet lost, slow down";
case ICMP_TSTAMP:
return "Timestamp Request";
case ICMP_TSTAMPREPLY:
return "Timestamp Reply";
case ICMP_IREQ:
return "Information Request";
case ICMP_IREQREPLY:
return "Information Reply";
case ICMP_UNREACH:
switch(code)
{
case ICMP_UNREACH_NET:
return "Unreachable Network";
case ICMP_UNREACH_HOST:
return "Unreachable Host";
case ICMP_UNREACH_PROTOCOL:
return "Unreachable Protocol";
case ICMP_UNREACH_PORT:
return "Unreachable Port";
case ICMP_UNREACH_NEEDFRAG:
return "Unreachable: Fragmentation needed";
case ICMP_UNREACH_SRCFAIL:
return "Unreachable Source Route";
case ICMP_UNREACH_NET_UNKNOWN:
return "Unknown Network";
case ICMP_UNREACH_HOST_UNKNOWN:
return "Unknown Host";
case ICMP_UNREACH_ISOLATED:
return "Unreachable: Isolated";
case ICMP_UNREACH_NET_PROHIB:
return "Prohibited network";
case ICMP_UNREACH_HOST_PROHIB:
return "Prohibited host";
case ICMP_UNREACH_FILTER_PROHIB:
return "Unreachable: Prohibited filter";
case ICMP_UNREACH_TOSNET:
return "Unreachable: Type of Service and Network";
case ICMP_UNREACH_TOSHOST:
return "Unreachable: Type of Service and Host";
case ICMP_UNREACH_HOST_PRECEDENCE:
return "Unreachable: Prec vio";
case ICMP_UNREACH_PRECEDENCE_CUTOFF:
return "Unreachable: Prec cutoff";
default:
return "Unreachable: Unknown Subtype";
}
break;
case ICMP_REDIRECT:
switch(code)
{
case ICMP_REDIRECT_NET:
return "Redirect: Network";
case ICMP_REDIRECT_HOST:
return "Redirect: Host";
case ICMP_REDIRECT_TOSNET:
return "Redirect: Type of Service and Network";
case ICMP_REDIRECT_TOSHOST:
return "Redirect: Type of Service and Host";
default:
return "Redirect: Unknown Subtype";
}
case ICMP_TIMXCEED:
switch(code)
{
case ICMP_TIMXCEED_INTRANS:
return "Timeout: TTL";
case ICMP_TIMXCEED_REASS:
return "Timeout: Fragmentation reassembly";
default:
return "Timeout: Unknown Subtype";
}
break;
default:
return "Unknown type";
}
}
uint16_t chksum(unsigned short *buf, int len)
{
uint32_t sum = 0;
while(len > 1) {
sum += *buf;
buf++;
len -= 2;
}
if(len)
sum += (*(uint8_t *)buf);
sum = (sum >> 16) + (sum & 0xFFFF);
sum = (sum >> 16) + sum;
return ~sum;
}
int ping_write(char *buf, int seq)
{
struct timeval tv;
struct icmp *icmp = (struct icmp *)buf;
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
icmp->icmp_id = ident;
icmp->icmp_seq = seq;
icmp->icmp_cksum = 0;
gettimeofday(&tv, NULL);
memcpy(buf + 8, &tv, sizeof(tv));
icmp->icmp_cksum = chksum((unsigned short *)icmp, ICMP_PACKET_SIZE);
// printf("send ICMP packet(type:%d, code:%d, id:%d, seq:%d, checksum:%d\n",
// icmp->icmp_type, icmp->icmp_code, icmp->icmp_id, icmp->icmp_seq, icmp->icmp_cksum);
}
int ping_read(void *buf, struct sockaddr_in answer)
{
uint16_t chk_sum;
struct timeval tv_send, tv_now;
unsigned int mini_sec;
struct ip *ip = (struct ip *)buf;
struct icmp *icmp = (struct icmp *)(buf + (ip->ip_hl << 2));
// printf("recv ICMP packet(type:%d, code:%d, id:%d, seq:%d, checksum:%d\n",
// icmp->icmp_type, icmp->icmp_code, icmp->icmp_id, icmp->icmp_seq, icmp->icmp_cksum);
if(icmp->icmp_id != ident) {
// printf("ERROR: bad icmp_id %d\n", icmp->icmp_id);
return -1;
}
if(icmp->icmp_type != ICMP_ECHOREPLY) {
printf("ERROR: %s\n", ping_desc(icmp->icmp_type, icmp->icmp_code));
return -1;
}
chk_sum = icmp->icmp_cksum;
icmp->icmp_cksum = 0;
if(chk_sum != chksum((unsigned short *)icmp, ICMP_PACKET_SIZE)) {
printf("ERROR: bad chksum\n");
return -1;
}
gettimeofday(&tv_now, NULL);
memcpy(&tv_send, ((char *)icmp + 8), sizeof(tv_send));
mini_sec = (tv_now.tv_sec - tv_send.tv_sec) * 1000000 + (tv_now.tv_usec - tv_send.tv_usec);
printf("%d bytes from %s: icmp_seq=%d, ttl=%d, time=%.3f ms\n",
(ip->ip_hl << 2) + 16,
inet_ntoa(answer.sin_addr),
icmp->icmp_seq, ip->ip_ttl, mini_sec/1000.0);
return 0;
}
int main(int argc, char *argv[])
{
struct hostent *host;
struct sockaddr_in dest;
struct sockaddr_in answer;
int answer_len = sizeof(answer);
int sock_raw_fd, seq=0;
char send_buf[ICMP_PACKET_SIZE];
char recv_buf[50];
if(argc != 2) {
printf("Usage: %s ipaddr/hostname\n", argv[0]);
exit(1);
}
if ((host = gethostbyname(argv[1])) == NULL) {
printf("ping: unknow host %s \n", argv[1]);
exit(1);
}
ident = getpid() & 0xFFFF;
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
memcpy(&dest.sin_addr, host->h_addr, sizeof(int));
dest.sin_port = ntohs(0);
if ((sock_raw_fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) {
perror("socket");
exit(1);
}
while(seq < 5) {
seq++;
// printf("\nid:%d\n", seq);
ping_write(send_buf, seq);
if (sendto(sock_raw_fd, (const char*)send_buf, ICMP_PACKET_SIZE, 0, (struct sockaddr *)&dest, sizeof(dest)) == -1) {
perror("sendto");
exit(1);
}
while(1) {
int ret = 0;
struct timeval tv;
fd_set readset;
FD_ZERO(&readset);
FD_SET(sock_raw_fd, &readset);
tv.tv_sec = TIMEOUT_SECONDS;
tv.tv_usec = 0;
if ((ret = select(sock_raw_fd+1, &readset, NULL, NULL, &tv)) == -1) {
perror("select");
exit(1);
}
if(ret == 0) {
printf("time out.\n");
break;
} else {
if(recvfrom(sock_raw_fd, recv_buf, 36, 0, (struct sockaddr *)&answer, &answer_len) == -1) {
perror("select");
exit(1);
} else {
if (ping_read(recv_buf, answer) == 0)
break;
}
}
}
sleep(1);
}
return 0;
}