Linux下Socket网络编程中ICMP的应用 (linux socket icmp)
随着互联网技术的不断发展,网络编程已成为了计算机科学不可或缺的一部分。而作为一个网络编程工程师,学习套接字编程是十分必要的。Linux下Socket网络编程可以说是互联网通信的基础,而网络通信中的ICMP协议,则是套接字编程的核心。
一、 ICMP简介
Internet 控制消息协议(Internet Control Message Protocol,简称 ICMP)是 TCP/IP 协议族的重要组成部分,用于网络设备之间进行错误报告传递、交换网络状态信息等。常常被用于网络管理、网络故障排查等领域,在网络编程中也广泛应用。
ICMP主要分为两类:一种是错误报告 ICMP 错误消息,另一种则是一些辅助信息 ICMP通知消息。在套接字编程中经常使用的,是ICMP错误消息,例如路由器发送 ICMP 给发送数据的主机,使其知道到达的地址不可达。
二、 套接字编程下的ICMP应用
在 Linux 下套接字编程中,ICMP 错误消息的处理是由内核完成的,而应用程序通过捕获对应的 ICMP 错误消息进行处理。这种方式被称为”Socket接收错误报告”。在套接字编程中,如果应用程序向一个未连接的IP地址发送数据,而这个IP地址无法到达,那么内核会自动回复一个 ICMP “目的地不可达”的信息。而我们可以通过捕捉这个回复信息,得知目的IP地址是否可达。
下面是一个ICMP的应用例程:
“`
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PACKET_SIZE 4096
#define MAX_WT_TIME 5
#define MAX_NO_PACKETS 3
char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
int sockfd,datalen = 56;
int nsend = 0,nreceived = 0;
struct sockaddr_in dest_addr;
struct sockaddr_in from;
unsigned short cal_chksum(unsigned short *addr,int len);
int pack(int pack_no)
{
int i,packsize;
struct icmp *icmp;
struct timeval *tval;
icmp = (struct icmp*)sendpacket;
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
icmp->icmp_cksum = 0;
icmp->icmp_seq = pack_no;
icmp->icmp_id = getpid();
packsize = 8 + datalen;
tval = (struct timeval *)icmp->icmp_data;
gettimeofday(tval,NULL);
icmp->icmp_cksum = cal_chksum((unsigned short *)icmp,packsize);
return packsize;
}
void send_packet()
{
int packetsize;
while(nsend
nsend++;
packetsize = pack(nsend);
if(sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr))
perror(“sendto error”);
continue;
}
}
}
void recv_packet()
{
int n,fromlen;
extern int errno;
fromlen = sizeof(from);
while(nreceived
if((n = recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr *)&from,&fromlen))
if(errno == EINTR)
continue;
perror(“recvfrom error”);
continue;
}
gettimeofday(&tvrecv,NULL);
unpack(recvpacket,n);
nreceived++;
}
}
int unpack(char *buf,int len)
{
int i,iphdrlen;
struct iphdr *ip;
struct icmp *icmp;
struct timeval *tvsend;
double rtt;
ip = (struct iphdr *)buf;
iphdrlen = ip->ihl
icmp = (struct icmp *)(buf + iphdrlen);
len -= iphdrlen;
if(len
fprintf(stderr,”ICMP packets\’s length is less than 8\n”);
return -1;
}
if((icmp->icmp_type == ICMP_ECHOREP) && (icmp->icmp_id == getpid())){
tvsend = (struct timeval *)icmp->icmp_data;
tv_sub(&tvrecv,tvsend);
rtt = tvrecv.tv_sec * 1000 + tvrecv.tv_usec / 1000.0;
printf(“%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms\n”,len,inet_ntoa(from.sin_addr),icmp->icmp_seq,ip->ttl,rtt);
}else{
printf(“none %d byte ICMP packet received from %s icmp_type=%d icmp_code=%d\n”,len,inet_ntoa(from.sin_addr),icmp->icmp_type,icmp->icmp_code);
}
}
unsigned short cal_chksum(unsigned short *addr,int len)
{
int nleft = len;
int sum = 0;
unsigned short *w = addr;
unsigned short answer = 0;
while(nleft > 1){
sum += *w++;
nleft -= 2;
}
if(nleft == 1){
*(unsigned char *)(&answer) = *(unsigned char *)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return answer;
}
int mn(int argc,char *argv[])
{
struct hostent *host;
struct timeval tv;
struct timezone tz;
int ch;
char buf[128];
opterr = 0;
while((ch = getopt(argc,argv,”t:”)) != -1){
switch(ch){
case ‘t’:
if(optarg == NULL){
fprintf(stderr,”Please input IP addresss or domn name.\n”);
exit(1);
}
if((host = gethostbyname(optarg)) == NULL){
perror(“gethostbyname error”);
exit(1);
}
break;
default:
printf(“-t \n”);
exit(1);
}
}
argc -= optind;
argv += optind;
if(argc != 0){
printf(“Usage:%s -t \n”,argv[0]);
exit(1);
}
if(host == NULL){
printf(“Please input IP addresss or domn name.\n”);
exit(1);
}
memset(&dest_addr,0,sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
memcpy(&dest_addr.sin_addr,host->h_addr,host->h_length);
dest_addr.sin_port = htons(0);
if((sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))
perror(“socket error”);
exit(1);
}
setuid(getpid());
printf(“PING %s (%s): %d data bytes\n”,host->h_name,inet_ntoa(dest_addr.sin_addr),datalen);
send_packet();
recv_packet();
printf(“\n— %s ping statistics —\n”,host->h_name);
printf(“%d packets tranitted, %d received, %%%d lost\n”,nsend,nreceived,(nsend-nreceived)/nsend*100);
return 0;
}
“`
这个例程是一个简单的Ping程序,可以根据域名或者IP地址获取目标主机的回应时间。该程序主要分为三个部分:发送数据包、接收错误报告、打印数据统计信息。
三、 一些需要注意的问题
在套接字编程中,需要注意以下问题:
1. 要使用ICMP协议,需要使用SOCK_RAW套接字类型,需要root权限运行。
2. 注意ICMP报文的格式,不同类型的ICMP报文格式不同,需要仔细调查。
3. 在接收ICMP错误报告时,需要注意可能会同时收到发送的数据包和接收的错误报告。因此需要仔细分析收到的数据包,以确定错误信息的来源。