Linux下实现ping程序
今天参照大神的代码实现了一个ping程序。
总体是先发送一个ping请求,然后循环四次监听返回。
send_ping函數
-
將icmp_hdr(icmp消息的header)的指針指向一片內存空間,然後定義各個屬性。通過memcpy函數將要發送的數據複製到data屬性中。
再通過sendto函數將icmp數據包發送到指定地址
sendto函數
#include <sys/types.h> #include <sys/socket.h> int sendto(int socketfd, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);
socketfd:socket套接字描述符
成功則返回發送的字符數,否則返回-1.
與之對應的是recvfrom函數
#include <sys/types.h> #include <sys/socket.h> int sendto(int socketfd, void* buffer, int len, unsigned int flags, const struct sockaddr * from, int fromlen);
通過套接字接收信息,存放到buffer中。
handle_packet函數
-
recv_reply函數將返回的ICMP消息存儲在recv_buf中,在處理返回結果時,將一個iphdr類型的指針指向這塊內存進行操作。
這塊內存信息包含的數據有IP信息頭部長度,數據部分長度。數據部分包含了icmp的頭部信息和數據信息。icmp的起始地址便是recvbuf + ip頭的長度。然後要計算校驗和,如果檢驗和不為0,說明信息出現了錯誤。
然後還要判斷icmp_id和icmp_type。
贴一下源码
#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netdb.h>
#include<errno.h>
#include<arpa/inet.h>
#include<signal.h>
#include<netinet/in.h>
#ifndef _LITTLE_ENDIAN_BITFIELD
#define _LITTLE_ENDIAN_BITFIELD
#endif
#define IP_HSIZE sizeof(struct iphdr) //定义IP_HSIZE为ip头部长度
#define IPVERSION 4 //定义IPVERSION为4,指出用ipv4
#define ICMP_ECHOREPLY 0 //Echo应答
#define ICMP_ECHO 8 //Echo请求
#define BUFSIZE 1500 //发送缓存最大值
#define DEFAULT_LEN 56 //ping 消息数据默认大小
//数据类型别名
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
//ICMP消息头部
struct icmphdr
{
u8 type;
u8 code;
u16 checksum;
union
{
struct
{
u16 id;
u16 sequence;
}echo;
u32 gateway;
struct
{
u16 unused;
u16 mtu;
}frag; //pmtu发现
}un;
u32 icmp_timestamp[2];//时间戳
//ICMP数据占位符
u8 data[0];
#define icmp_id un.echo.id
#define icmp_seq un.echo.sequence
};
#define ICMP_HSIZE sizeof(struct icmphdr)
struct iphdr
{
#if defined _LITTLE_ENDIAN_BITFIELD
u8 hlen:4,
ver: 4;
#elif defined _BIG_ENFIAN_BITFELD
u8 ver:4,
hlen:4;
#endif
u8 tos;
u16 tot_len;
u16 id;
u16 frag_off;
u8 ttl;
u8 protocol;
u16 check;
u32 saddr;
u32 daddr;
};
char hello[]="hello this is a ping test.";
char *hostname; //被ping的主机
int datalen=DEFAULT_LEN;//ICMP消息携带的数据长度
char sendbuf[BUFSIZE];
char recvbuf[BUFSIZE];
int nsent;//发送的ICMP消息序号
int nrecv;
pid_t pid;//ping程序的进程pid
struct timeval recvtime; //收到ICMP应答的时间戳
int sockfd; //发送和接收原始套接字
struct sockaddr_in dest;//被ping主机的ip
struct sockaddr_in from;//发送ping应答消息的主机ip
struct sigaction act_alarm;
struct sigaction act_int;
//设置的时间是一个结构体,倒计时设置,重复倒时,超时值设为1秒
struct itimerval val_alarm;
void alarm_handler(int);//SIGALRM处理程序
void int_handler(int);//SIGINT处理程序
void set_sighandler();//设置信号处理程序
void send_ping();//发送ping消息
void recv_reply();//接收ping应答
u16 checksum(u8 *buf,int len);//计算校验和
int handle_pkt();//ICMP应答消息处理
void get_statistics(int ,int);//统计ping命令的检测结果
void bail(const char *);//错误报告
void send_ping() {
struct iphdr* ip_hdr;
struct icmphdr* icmp_hdr;
icmp_hdr = (struct icmphdr*)(sendbuf);
icmp_hdr->type = ICMP_ECHO;
icmp_hdr->code = 0;
icmp_hdr->icmp_id = pid; //icmp_id = un.id,means process id
icmp_hdr->icmp_seq = nsent++;
gettimeofday((struct timeval*)icmp_hdr->icmp_timestamp,NULL);
int len = ICMP_HSIZE + strlen(hello);
icmp_hdr->checksum = 0;
icmp_hdr->checksum = checksum((u8 *)icmp_hdr, len);
sendto(sockfd, sendbuf, len, 0, (struct sockaddr*)&dest, sizeof(dest));
}
void recv_reply() {
socklen_t len;
int n = 0;
nrecv = 0;
int errno;
len = sizeof(from);
while (nrecv < 4) {
if ((n = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr*)&from, &len)) < 0) {
if (errno == EINTR) {
continue;
}
bail("recv from error");
}
gettimeofday(&recvtime, NULL);
if (handle_pkt())
continue;
nrecv++;
}
get_statistics(nsent, nrecv);
}
u16 checksum(u8* buf, int len) {
u32 sum=0;
u16 *cbuf;
cbuf=(u16 *)buf;
while(len>1)
{
sum+=*cbuf++;
len-=2;
}
if(len)
sum+=*(u8 *)cbuf;
sum=(sum>>16)+(sum & 0xffff);
sum+=(sum>>16);
return ~sum;
}
//handle ip response packet
int handle_pkt() {
struct iphdr* ip;
ip = (struct iphdr*)recvbuf;
int ip_hlen = ip->hlen << 2;
u16 ip_datalen = ntohs(ip->tot_len) - ip_hlen;
struct icmphdr* icmp;
icmp = (struct icmphdr*)(recvbuf + ip_hlen);
u16 sum = (u16)checksum((u8*)icmp, ip_datalen);
if (sum) {
printf("check is not equal to 0\n");
return -1;
}
if (icmp->type != ICMP_ECHOREPLY) {
return -1;
}
if (icmp->icmp_id != pid) {
return -1;
}
struct timeval* sendtime = (struct timeval*)icmp->icmp_timestamp;
double rtt;//round-trip time
rtt = ((&recvtime)->tv_sec - sendtime->tv_sec) * 1000 + ((&recvtime)->tv_usec - sendtime->tv_usec) / 1000.0;
printf("%dbytes from %s:icmp_seq = %u, ttl = %d, rrt = %.3f ms\n",ip_datalen, inet_ntoa(from.sin_addr), nsent, ip->ttl, rtt);
return 0;
}
//signal handler
void set_sighandler() {
act_alarm.sa_handler=alarm_handler;
printf("alarm handle signal: %d\n", SIGALRM);
if(sigaction(SIGALRM,&act_alarm,NULL)==-1) //sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum指所要捕获信号或忽略的信号,&act代表新设置的信号共用体,NULL代表之前设置的信号处理结构体。这里判断对信号的处理是否成功。
bail("SIGALRM handler setting fails.");
act_int.sa_handler=int_handler;
printf("int handler handle signal: %d\n", SIGALRM);
if(sigaction(SIGINT,&act_int,NULL)==-1)
bail("SIGALRM handler setting fails.");
}
void get_statistics(int nsent, int nrecv) {
printf("---%s ping statistics-----\n",inet_ntoa(dest.sin_addr));
printf("%d packets transmitted,%d received, %0.0f%% loss\n",nsent, nrecv, 1.0 * (nsent - nrecv) / nsent * 100);
}
//error report
void bail(const char* incident) {
fputs(strerror(errno), stderr);
fputs(":", stderr);
fputs(incident, stderr);
fputc('\n', stderr);
exit(1);
}
//interupt signal process program
void int_handler(int sig) {
get_statistics(nsent, nrecv);
close(sockfd);
exit(1);
}
void alarm_handler(int signo) {
send_ping();
}
int main(int argc, char** argv) {
val_alarm.it_interval.tv_sec = 1;
val_alarm.it_interval.tv_usec = 0;
val_alarm.it_value.tv_sec = 0;
val_alarm.it_value.tv_usec = 1;
struct hostent* host; //#include <netdb.h>
int on = 1;
if ((host = gethostbyname(argv[1])) == NULL) {
perror("can not understand the host name");
exit(1);
}
hostname = argv[1];
memset(&dest, 0, sizeof(dest));
dest.sin_family = PF_INET;
dest.sin_port = ntohs(0);
dest.sin_addr = *(struct in_addr *)host->h_addr_list[0];//h_addr_list is char** type
setuid(getuid());//grant user root authority
if ((sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
perror("raw socket created error");
exit(1);
}
pid = getpid();
printf("Pid: %d\n", pid);
set_sighandler();
printf("Ping %s(%s): %d bytes data in ICMP packets.\n", argv[1], inet_ntoa(dest.sin_addr), datalen);
if ((setitimer(ITIMER_REAL, &val_alarm, NULL)) == -1)
bail("setitimer falied");
recv_reply();
return 0;
}
WELCOME TO THE MACHINE