博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
TCP/IP详解卷1:第六章(ICMP:internet控制报文协议)
阅读量:7044 次
发布时间:2019-06-28

本文共 7464 字,大约阅读时间需要 24 分钟。

  hot3.png

1. ICMP基础知识

ICMP经常被认为是IP层的一个组成部分。它传递差错报文以及其他需要注意的信息。ICMP报文通常被IP层或更高层协议(TCP或UDP)使用。一些ICMP报文把差错报文返回给用户进程。

1) ICMP报文的类型

而具体的类型和代码如下:
在对ICMP差错报文进行响应时,永远不会生生成另一份ICMP差错报文(否则会无休止的产生差错报文)。下面这种情况都不会导致产生ICMP差错报文:
1) ICMP差错报文(ICMP查询报文可能会产生ICMP差错报文)
2) 目的地址是广播地址或者多播地址的IP数据报
3) 作为链路层广播的数据报
4) 不是IP分片的第一片
5) 源地址不是单个主机的数据报。这就是说,源地址不能为零地址,环回地址,广播地址或多播地址。
这些规则是为了防止过去允许ICMP差错报文对广播分组响应所带来的广播风暴

2. ICMP地址掩码的请求与应答/ICMP时间戳请求与应答

这部分直接看代码,会比较直观一些:
icmpaddrmask.c:
/* * Issue an ICMP address mask request and print the reply. * * This program originated from the public domain ping program written * by Mike Muuss. * * You must be superuser to run this program (or it must be suid to root) * since it requires a raw socket. */#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* #include
*/#include
#include
#include
#include
#include
#include
//DEFDATALEN为子网掩码的请求报文长度(类型(1字节) + 代码(1字节) + 校验和(2字节) + 标识符(2字节) + 序列号(2字节) + 32位子网掩码(4字节))#define DEFDATALEN (12) /* default data length */#define MAXIPLEN 60 //IP数据报的最大长度(因为胃4bit,最大为15,所以共有15 * 32bit(IP首部长度)共60字节)#define MAXICMPLEN 76 //为什么ICMP的最大长度为76#define MAXPACKET (65536 - 60 - 8)/* max packet size */struct sockaddr whereto; /* who to send request to */int datalen = DEFDATALEN;int s;u_char outpack[MAXPACKET];char *hostname; //存储输入的IP地址(点分十进制)u_long inet_addr();char *inet_ntoa();void sig_alrm(int);int response = 0;main(argc, argv)int argc;char **argv;{ int i, ch, fdmask, hold, packlen, preload; extern int errno; struct hostent *hp; struct sockaddr_in *to; struct protoent *proto; u_char *packet; char *target, hnamebuf[MAXHOSTNAMELEN], *malloc(); if (argc != 2) exit(1); target = argv[1]; bzero((char *)&whereto, sizeof(struct sockaddr)); to = (struct sockaddr_in *)&whereto; to->sin_family = AF_INET; /* try to convert as dotted decimal address, else if that fails assume it's a hostname */ to->sin_addr.s_addr = inet_addr(target); //说明target是一个IP地址 if (to->sin_addr.s_addr != (u_int)-1) hostname = target; else { //说明target是一个域名或主机名 hp = gethostbyname(target); if (!hp) { fprintf(stderr, "unknown host %s\n", target); exit(1); } to->sin_family = hp->h_addrtype; bcopy(hp->h_addr, (caddr_t)&to->sin_addr, hp->h_length); strncpy(hnamebuf, hp->h_name, sizeof(hnamebuf) - 1); hostname = hnamebuf; } packlen = datalen + MAXIPLEN + MAXICMPLEN; //=12 + 60 + 76 if ( (packet = (u_char *)malloc((u_int)packlen)) == NULL) { fprintf(stderr, "malloc error\n"); exit(1); } if ( (proto = getprotobyname("icmp")) == NULL) { fprintf(stderr, "unknown protocol icmp\n"); exit(1); } if ( (s = socket(AF_INET, SOCK_RAW, proto->p_proto)) < 0) { perror("socket"); /* probably not running as superuser */ exit(1); } /* * We send one request, then wait 5 seconds, printing any * replies that come back. This lets us send a request to * a broadcast address and print multiple replies. */ signal(SIGALRM, sig_alrm); alarm(5); /* 5 second time limit */ sender(); /* send the request */ for (;;) { struct sockaddr_in from; int cc, fromlen; fromlen = sizeof(from); if ( (cc = recvfrom(s, (char *)packet, packlen, 0, (struct sockaddr *)&from, &fromlen)) < 0) { if (errno == EINTR) continue; perror("recvfrom error"); continue; } procpack((char *)packet, cc, &from); }}/* * Send the ICMP address mask request. */sender(){ int i, cc; struct icmp *icp; //所发送的ICMP地址掩码请求数据(后期可指定标识符icmp_id和序列号icmp_seq来把应答和请求相匹配) icp = (struct icmp *)outpack; icp->icmp_type = ICMP_MASKREQ; //地址掩码请求 icp->icmp_code = 0; //代码为0 icp->icmp_cksum = 0; /* compute checksum below */ icp->icmp_seq = 12345; /* seq and id must be reflected */ //序列号 icp->icmp_id = getpid(); //标识符---之所以把进程ID设置为标识符,因为同时运行的程序组不可能存在进程ID相同的两个进程 icp->icmp_mask = 0; cc = ICMP_MASKLEN; /* 12 = 8 bytes of header, 4 bytes of mask */ /* compute ICMP checksum here */ icp->icmp_cksum = in_cksum((u_short *)icp, cc); //往指定的目的地址whereto发送ICMP地址掩码请求 i = sendto(s, (char *)outpack, cc, 0, &whereto, sizeof(struct sockaddr)); if (i < 0 || i != cc) { if (i < 0) perror("sendto error"); printf("wrote %s %d chars, ret=%d\n", hostname, cc, i); }}/* * Process a received ICMP message. */procpack(buf, cc, from)char *buf;int cc;struct sockaddr_in *from;{ int i, hlen; struct icmp *icp; struct ip *ip; struct timeval tvdelta; /* Check the IP header */ ip = (struct ip *)buf; hlen = ip->ip_hl << 2; //得到IP数据报的长度(IP数据报的前两个字节为:版本 + IP首部长度) if (cc < hlen + ICMP_MINLEN) { fprintf(stderr, "packet too short (%d bytes) from %s\n", cc, inet_ntoa(*(struct in_addr *)&from->sin_addr.s_addr)); return; } /* Now the ICMP part */ cc -= hlen; icp = (struct icmp *)(buf + hlen); /* With a raw ICMP socket we get all ICMP packets that come into the kernel. */ //如果得到的是子网掩码的应答 //不知道为什么ICMP_MASKREPLY的值不等于17 if (ICMP_MASKREPLY != 17) printf("error!\n"); if (icp->icmp_type == 17) { if (cc != ICMP_MASKLEN) printf("cc = %d, expected cc = 12\n", cc); //判断序列号是否一致 if (icp->icmp_seq != 12345) printf("received sequence # %d\n", icp->icmp_seq); //判断标识符是否一致 if (icp->icmp_id != getpid()) printf("received id %d\n", icp->icmp_id); //如果标识符一致并且序列号一致,则打印其子网掩码和地址 printf("received mask = %08x, from %s\n", ntohl(icp->icmp_mask), inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr)); response++; } /* We ignore all other types of ICMP messages */}/* * in_cksum -- * Checksum routine for Internet Protocol family headers (C Version) */in_cksum(addr, len) u_short *addr; int len;{ register int nleft = len; register u_short *w = addr; register int sum = 0; u_short answer = 0; /* * Our algorithm is simple, using a 32 bit accumulator (sum), we add * sequential 16 bit words to it, and at the end, fold back all the * carry bits from the top 16 bits into the lower 16 bits. */ while (nleft > 1) { sum += *w++; nleft -= 2; } /* mop up an odd byte, if necessary */ if (nleft == 1) { *(u_char *)(&answer) = *(u_char *)w ; sum += answer; } /* add back carry outs from top 16 bits to low 16 bits */ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ sum += (sum >> 16); /* add carry */ answer = ~sum; /* truncate to 16 bits */ return(answer);}voidsig_alrm(int signo){ if (response == 0) { printf("timeout\n"); exit(1); } exit(0); /* we got one or more responses */}
可以通过单步调试的方式,进行学习。程序运行如下:
root@ThinkPad-T430i:/home/leichaojian# ./icmpaddrmask 192.168.0.5error!received mask = 00000000, from 192.168.0.5

3. ICMP端口不可达

UDP的规则之一是:如果收到一份UDP数据报而目的端口与某个正在使用的进程不相符,那么UDP返回一个ICMP不可达报文,可以用TFTP来强制生成一个端口不可达报文。
leichaojian@ThinkPad-T430i:~$ tftptftp> connect svr4 8888tftp> get temp.fooTransfer timed out.
我们执行如下命令:
root@ThinkPad-T430i:/home/leichaojian# tcpdump -e > temp.foo
我们把temp.foo里面关于8888端口的日志打印出来如下:
20:51:54.096199 00:21:cc:cc:05:d4 (oui Unknown) > c8:3a:35:64:96:d8 (oui Unknown), ethertype IPv4 (0x0800), length 62: 192.168.0.5.45810 > 218.30.64.194.8888: UDP, length 2020:51:59.096378 00:21:cc:cc:05:d4 (oui Unknown) > c8:3a:35:64:96:d8 (oui Unknown), ethertype IPv4 (0x0800), length 62: 192.168.0.5.45810 > 218.30.64.194.8888: UDP, length 2020:52:04.096526 00:21:cc:cc:05:d4 (oui Unknown) > c8:3a:35:64:96:d8 (oui Unknown), ethertype IPv4 (0x0800), length 62: 192.168.0.5.45810 > 218.30.64.194.8888: UDP, length 2020:52:09.096700 00:21:cc:cc:05:d4 (oui Unknown) > c8:3a:35:64:96:d8 (oui Unknown), ethertype IPv4 (0x0800), length 62: 192.168.0.5.45810 > 218.30.64.194.8888: UDP, length 20
从中我们可以看出UDP的数据报长度为20.下图为ICMP完整长度:
而ICMP不可达报文的一般格式如下:

转载于:https://my.oschina.net/voler/blog/336764

你可能感兴趣的文章
如何设置打开PL/SQL预加载之前的sql文本内容
查看>>
Struts2笔记(1)-----Struts2简介与工作原理
查看>>
好程序员教你正确规避java编程中的bug
查看>>
C#编程--流程控制switch case
查看>>
maven的中央仓库地址
查看>>
分析你的第一个 Android 程序
查看>>
自制应用层协议的编写
查看>>
Python - 关于Python的变量
查看>>
解决exp无法导出问题
查看>>
【学以致用】Django之(URL)路由系统
查看>>
XML(eXtensible Markup Language)速成
查看>>
apache+mysql+php 安装教程
查看>>
关于删除oracle 用户及数据 出现 ORA-00604
查看>>
solr 为单独的core导入jar
查看>>
cut用法
查看>>
NO.118 不懂语言代码,超级菜鸟的建站分享(一):建站流程。
查看>>
iOS SDK计算SHA1和MD5
查看>>
利用regedit,来禁止使用regedit.
查看>>
Linux设置本地yum源
查看>>
laravel身份验证-Auth的使用
查看>>