本文共 7820 字,大约阅读时间需要 26 分钟。
实验7 ICMP 协议程序设计 |
实验要求: |
1 根据ICMP 协议,编写一个类似ping 的程序,具体的要求是:程序发出一个ICMP 包到 目的IP,然后等待接收回应的包(echo)。 2 根据ICMP 协议,向一个固定的IP 发出一系列的TTL 逐次增大的ICMP 的包,这些包的 TTL 每通过一个路由器就会减小1,当TTL 为0 的时候,路由器会发回一个ICMP 不可达 的包,通过该包可以获得路由器的IP 地址,通过这个原理编写一个类似tracert 的程序 |
实验内容: 1、ICMP 包的头部结构和IP 包头部结构 struct ICMP { |
unsigned char type;//类型 unsigned char code;//代码 unsigned short checksum;//首部校验和 unsigned short id;//标识 unsigned short seq;//序列号 unsigned long timestamp;//时间戳 |
}; |
struct IP { |
unsigned char VIHL;//版本和首部长度 unsigned char ToS; //服务类型 unsigned short TotalLen;//总长度 unsigned short ID;//标识号 unsigned short Frag_Flags;//段偏移量 unsigned char TTL;//生存时间 unsigned char protocol;//协议 unsigned short Checksum;//首部校验和 struct in_addr SrcIP; struct in_addr DestIP; |
}; |
2、ping 程序的步骤: Myping() |
{ |
1 |
设置ICMP 头部,具体为: ICMP 设置为0 设置type 域为8 设置code 域为0 设置id 为进程号 设置seq 为1 计算校验值,设置checksum 为校验值 |
2 发送ICMP 包到目的IP 3 等待回收包,判断id 和seq 是否对应发出的ICMP 包,若是,则可判断ping 通,否则超 时报错 } |
3 tracer 的原理,对一个固定的IP 地址发出一个系列的ICMP 包 1 tracer 程序步骤: For(i=1;i<63;i++) { |
设置ICMP 头部,具体为: |
ICMP 设置为0 设置type 域为8 设置code 域为0 设置id 为进程号 设置seq 为1 计算校验值,设置checksum 为校验值 设置TTL 为i 的值,setsockopt(sock,IPPROTO_IP,IP_TTL,&i,sizeof(int)); 发出ICMP 包, 等待收到回应包,打印出回应包的源IP 地址 |
} |
附录: Myping 程序: #include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #include <string.h> #include <sys/un.h> |
#include <sys/time.h> #include <sys/ioctl.h> #include <unistd.h> #include <netinet/in.h> #include <netdb.h> typedef struct sockaddr SA; |
#define ICMP_ECHOREPLY 0 #define ICMP_DESTUNREACH 3 #define ICMP_ECHO 8 #define ICMP_TIMEOUT 11 #define MAX_HOPS 30 #define ICMP_PACKET_MIN 8 #define ICMP_PACKET_SIZE 32 #define MAX_PACKET_SIZE 1024 |
struct ICMP { |
unsigned char type;//类型 unsigned char code;//代码 unsigned short checksum;//首部校验和 unsigned short id;//标识 unsigned short seq;//序列号 unsigned long timestamp;//时间戳 |
}; |
struct IP { |
unsigned char VIHL;//版本和首部长度 unsigned char ToS; //服务类型 unsigned short TotalLen;//总长度 unsigned short ID;//标识号 unsigned short Frag_Flags;//段偏移量 unsigned char TTL;//生存时间 unsigned char protocol;//协议 unsigned short Checksum;//首部校验和 struct in_addr SrcIP; struct in_addr DestIP; |
}; void show32(char * buf,int len) { |
//展示整个数据包,其中buf 为首地址,len 为长度 printf("包为:\r\n"); int i=0; for(;i<len;i++) printf("%d ",buf[i]); |
printf("\r\n"); |
} void err_sys(const char *errmsg) { printf("%s\r\n",errmsg); exit(1); |
}; void send_echo_req(int sockfd, struct sockaddr_in *dstaddr); uint16_t in_cksum(uint16_t *addr, int len); void recv_echo_reply(int sockfd); int main(int argc, char **argv) { int sockfd; struct sockaddr_in dstaddr; |
if ((sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)//产生ICMP 包 err_sys("socket"); bzero(&dstaddr, sizeof(dstaddr)); dstaddr.sin_family = AF_INET; dstaddr.sin_port = htons(0); if (inet_pton(AF_INET, argv[1], &dstaddr.sin_addr) <= 0) err_sys("inet_pton"); send_echo_req(sockfd, &dstaddr); recv_echo_reply(sockfd); exit(0); |
} void send_echo_req(int sockfd, struct sockaddr_in *dstaddr) { char buf[100]; size_t len = sizeof(struct ICMP); struct ICMP *icmp; socklen_t dstlen = sizeof(struct sockaddr_in); bzero(buf, sizeof(buf)); |
//下面设置icmp 头部信息 icmp = (struct icmp *)buf; |
icmp->type = ICMP_ECHO; //类型设置为8 |
icmp->code = 0;//代码设置为0 icmp->id = getpid();//将id 设置为进程号 icmp->seq = 1;//序号随机找的一个整数 icmp->checksum = in_cksum((uint16_t *) icmp, sizeof(struct ICMP));//计算校验和 //将ICMP 包发出去 if (sendto(sockfd, buf, len, 0, (SA *)dstaddr, dstlen) == -1) err_sys("sendto"); |
} void recv_echo_reply(int sockfd) |
{ |
char buf[100]; ssize_t n; struct IP *ip; struct ICMP *icmp; while (1) |
{ |
alarm(5); /* set timeout 超过5 秒则退出程序*/ struct sockaddr_in peer; int len=sizeof peer; int ret=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&peer,&len); if(ret<0)err_sys("recvfrom error!\r\n"); ip = (struct IP *)buf; if (ip->protocol != IPPROTO_ICMP) |
{ |
fprintf(stderr, "protocol error."); exit(1); |
} icmp = (struct ICMP *)(buf + sizeof(struct IP)); if (icmp->type == ICMP_ECHOREPLY) { |
//判断该ICMP 包是否匹配 if (icmp->id != getpid()) { |
fprintf(stderr, "not this process."); exit(1); } |
else |
{ |
printf("destinationhostisalive.from icmp->type=%d",inet_ntoa(peer.sin_addr),htons(peer.sin_port),icmp->type); break; } } |
} |
} uint16_t in_cksum(uint16_t *addr, int len) { |
//该函数用来计算校验和 int nleft = len; uint32_t sum = 0; uint16_t *w = addr; uint16_t answer = 0; while (nleft > 1) |
{ |
sum += *w++; nleft -= 2; |
} if (nleft == 1) { |
%s:%d |
*(unsigned char *)(&answer) = *(unsigned char *)w ; sum += answer; |
} sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return(answer); |
} |
2 mytracert 程序 #include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #include <string.h> #include <sys/un.h> #include <sys/time.h> #include <sys/ioctl.h> |
#include <unistd.h> #include <netinet/in.h> #include <netdb.h> |
//定义ICMP 信息类型 #define ICMP_ECHOREPLY 0 #define ICMP_DESTUNREACH 3 #define ICMP_ECHO 8 #define ICMP_TIMEOUT 11 #define MAXHOPS 30 #define BUFSIZE 512 |
struct IP { |
unsigned char VIHL;//版本和首部长度 unsigned char ToS; //服务类型 unsigned short TotalLen;//总长度 unsigned short ID;//标识号 unsigned short Frag_Flags;//段偏移量 unsigned char TTL;//生存时间 unsigned char Protocol;//协议 unsigned short Checksum;//首部校验和 struct in_addr SrcIP; struct in_addr DestIP; |
}; struct ICMP { |
unsigned char Type;//类型 |
unsigned char Code;//代码 unsigned short Checksum;//首部校验和 unsigned short ID;//标识 unsigned short Seq;//序列号 unsigned long Timestamp;//时间戳 |
}; char buf[BUFSIZE]; unsigned short checksum(u_short *buffer,int len) |
{ |
register int sum=0; register int nleft=len; register unsigned short *w=buffer; register unsigned short answer; |
//使用32 位的累加器,进行16 位的反馈计算 while(nleft>1) { sum+=*w++; nleft-=2; |
} |
//补全奇数位 if(nleft==1) { unsigned short u=0; *(unsigned char*)(&u)=*(unsigned char*)w; sum+=u; } |
//将反馈的16bit 从高位移动到低位 sum=(sum>>16)+(sum&0xffff); |
sum+=(sum>>16); answer=~sum; return (answer); |
}; |
void show32(char * buf,int s) { int i=0; |
printf("\r\n 包为:\r\n"); for(;i<s;i++) |
{ |
printf("%d ",buf[i]); |
}; printf("\r\n"); |
}; |
void err_sys(const char *errmsg) { printf("%s\r\n",errmsg); exit(1); }; |
int main(int argc,char * argv[]) { |
int ttl=1; struct ICMP *icmp,tagICMP; icmp=&tagICMP; |
if(argc<2) err_sys("请指定目标机器地址\r\n"); |
int sock=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);//创建ICMP 类型的原始套接字 int timeout=10000; |
//设置接收超时时间为1 秒 setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)); setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout)); |
//设置接收端地址 struct sockaddr_in dest; dest.sin_family=AF_INET; struct hostent *hp=gethostbyname(argv[1]); |
if(hp==NULL)err_sys("无法解析地址\r\n"); |
dest.sin_addr.s_addr=*((int*)hp->h_addr); //memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length); |
dest.sin_port=0; printf("we are find %s\r\n",inet_ntoa(dest.sin_addr)); int seq_no=1; for(ttl=1;ttl<MAXHOPS;ttl++) { |
//设置ICMP 首部数据段 icmp->Type=ICMP_ECHO;//ICMP_ECHO 为8 icmp->Code=0; icmp->Checksum=0; icmp->ID=getpid(); |
icmp->Seq=seq_no++; icmp->Timestamp=0; icmp->Checksum=checksum((u_short*)icmp,sizeof(struct ICMP)); //show32(icmp,sizeof(struct ICMP)); int nTTL=ttl; |
setsockopt(sock,IPPROTO_IP,IP_TTL,&nTTL,sizeof(int));//设置TTL |
sendto(sock,icmp,sizeof(struct ICMP),0,(struct sockaddr*)&dest,sizeof(dest));//发送ICMP 到目标主机 |
struct sockaddr_in src; int len=sizeof src; |
int retlen=recvfrom(sock,buf,BUFSIZE,0,(struct sockaddr*)&src,&len);//接收从目标机或者 路由返回的数据 //show32(buf,retlen); |
struct IP *echoip=(struct IP*)buf; struct ICMP *echoicmp=(struct ICMP*)(buf+sizeof(struct IP)); if(echoip->Protocol!=IPPROTO_ICMP) err_sys("protocol error!\r\n"); if(echoicmp->Type==ICMP_ECHOREPLY) { if(echoicmp->ID!=getpid()) err_sys("not same process!\r\n"); else { |
printf("%s reached ttl=%d\r\n",inet_ntoa(src.sin_addr),ttl); break; |
} |
} if(echoicmp->Type==ICMP_TIMEOUT ) { printf("%s reached ttl=%d\r\n",inet_ntoa(src.sin_addr),ttl); } if(echoicmp->Type==ICMP_DESTUNREACH) { |
printf("dest unreach\r\n"); break; |
} |
sleep(1);//等待1 秒钟 |
} |
return 0; |
} |
本文转自陈仲阳0 51CTO博客,原文链接:http://blog.51cto.com/wolfword/1240348
转载地址:http://cfnbx.baihongyu.com/