简单的整理了一个以前用的ping程序, 个人觉得对初学者学习网络协议还是有一定帮助的,最好是利用抓包工具查看下具体的数据报文。
/*
* ping.cpp
*
* Created on: Sep 10, 2014
* Author: xtank.nie@gmail.com
*/
#include <sys/time.h>
#include <netinet/ip_icmp.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#define SEND_PACKET_NUM (1)
int calculate_checksum(unsigned short *packet, int length)
{
int num_left = length;
int sum = 0;
unsigned short *w = packet;
unsigned short cksum = 0;
while (num_left > 1)
{
sum += *w++;
num_left -= 2;
}
if (num_left == 1)
{
/* 如果是奇数个字节,要在后面补上一个字节的0 */
*((unsigned char *)&cksum) = *((unsigned char *)w);
sum += cksum;
}
sum = (sum>>16) + (sum & 0xffff);
sum += (sum >> 16);
cksum= ~sum;
return cksum;
}
int crate_icmp_packet(char *packet_buf, uint32_t packet_no)
{
struct icmp *icmp_st;
int data_size = 56;
int packet_size;
//head
icmp_st = (struct icmp *)packet_buf;
icmp_st->icmp_type = ICMP_ECHO; //请求回送
icmp_st->icmp_code = 0;
icmp_st->icmp_cksum = 0;
icmp_st->icmp_seq = packet_no;
icmp_st->icmp_id = getpid();
//data
gettimeofday((struct timeval *)icmp_st->icmp_data, NULL);
packet_size = 8 + data_size;
icmp_st->icmp_cksum = calculate_checksum((unsigned short *)packet_buf, packet_size);
return packet_size;
}
int send_packet(int socket_fd, struct sockaddr *addr, char *packet_buf, int packet_no)
{
int packet_size;
packet_size = crate_icmp_packet(packet_buf, packet_no);
if ((packet_size = sendto(socket_fd, packet_buf, packet_size, 0, addr, sizeof(struct sockaddr_in))) == -1)
{
perror("sendto:");
return (-1); /* error */
}
//printf("send pack size=%d\n", packet_size);
return 0; /* success */
}
void calculate_interval(struct timeval *recv_time, struct timeval *send_time)
{
if ((recv_time->tv_usec -= send_time->tv_usec) < 0)
{
recv_time->tv_sec -= 1;
recv_time->tv_usec += 1000000;
}
recv_time->tv_sec -= send_time->tv_sec;
}
int unpack(struct sockaddr_in *from, char *packet_buf, int packet_size,
struct timeval *recv_time, double *rtt ,unsigned int *ttl)
{
struct ip *ip_st;
struct icmp *icmp_st;
int ipheader;
ip_st = (struct ip *)packet_buf;
ipheader = (ip_st->ip_hl << 2);
icmp_st = (struct icmp *)(packet_buf + ipheader);
packet_size -= ipheader;
if (packet_size < 8)
{
printf( "recvfrom packet_size < 8!\n");
return (-1); /* error */
}
if ((icmp_st->icmp_type == ICMP_ECHOREPLY) && (icmp_st->icmp_id == getpid()))
{
calculate_interval(recv_time, (struct timeval *)icmp_st->icmp_data);//收到的时间与发送的时间
*rtt = (recv_time->tv_sec * 1000) + (recv_time->tv_usec / 1000);
*ttl = ip_st->ip_ttl;
printf("%d byte from %s: icmp_seq = %u, ttl = %d, rtt = %.3fms\n",
packet_size, inet_ntoa(from->sin_addr), icmp_st->icmp_seq, ip_st->ip_ttl, *rtt);
return 0;
}
else
{
printf( "unpack error! icmp_type is %d, icmp_id is %d\r\n", (icmp_st->icmp_type == ICMP_ECHOREPLY), (icmp_st->icmp_id == getpid()));
return -1;
}
}
int recv_packet(int socket_fd, char *packet_buf, int *length, double *rtt, unsigned int *ttl)
{
int nbyte;
struct sockaddr_in from_addr;
struct timeval recv_time;
socklen_t size = sizeof(struct sockaddr_in);
if ((nbyte = recvfrom(socket_fd, packet_buf, *length, 0, (struct sockaddr *)&from_addr, &size)) == -1)
{
printf( "recvfrom return -1!\n" );
return (-1);
}
else
{
;//printf("recv data len=%d\n", nbyte);
}
*length = nbyte;
gettimeofday(&recv_time, NULL);
if (unpack(&from_addr, packet_buf, nbyte, &recv_time, rtt, ttl) != 0)
{
return (-1);
}
return 0;
}
int ping(const char *addr)
{
unsigned long inaddr = 0;
struct sockaddr_in dest_addr;
struct hostent *host;
int socket_ping = -1;
int packet_size = 2048;
char send_pac[2048] = {0};
char recv_pac[2048] = {0};
double iTimespend;
unsigned int iTTL, send_packet_cnt = 0, recv_packet_cnt = 0;;
memset(&dest_addr, 0x0, sizeof(struct sockaddr_in));
dest_addr.sin_family = AF_INET;
if ((inaddr = inet_addr(addr)) == INADDR_NONE)
{
if ((host = gethostbyname(addr)) == NULL)
{
fprintf(stderr, "gethostbyname error.\n");
return -1;
}
memcpy((char *)&(dest_addr.sin_addr), host->h_addr, host->h_length);
}
else
{
dest_addr.sin_addr.s_addr = inaddr;
}
if((socket_ping = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
{
perror("socket:");
return -1;
}
printf("Ping %s(%s): %d bytes data in ICMP packets.\n", addr, inet_ntoa(dest_addr.sin_addr), 56);
fd_set read_set;
struct timeval timeout;
int i = 1, ret;
for (i = 1; i <=SEND_PACKET_NUM; i++)
{
if (send_packet(socket_ping, (struct sockaddr *)&dest_addr, send_pac, i) == -1)
{
printf("send_packet error\n");
close(socket_ping);
return -1;
}
else
{
++send_packet_cnt;
}
FD_ZERO(&read_set);
FD_SET(socket_ping, &read_set);
timeout.tv_sec = 1;
timeout.tv_usec = 0;
ret = select(socket_ping+1, &read_set, NULL, NULL, &timeout);
if (ret > 0)
{
if (recv_packet(socket_ping, recv_pac, &packet_size, &iTimespend, &iTTL) == -1)
{
printf("recv_packet error\n");
}
else
{
++recv_packet_cnt;
}
}
else if (ret == 0)
{
printf("Request time out.\n");
continue;
}
else
{
perror("select error.");
continue;
}
}
close(socket_ping);
//statistics
printf("---- (%s) ping statistics ----\n", addr);
printf("%d packets transmitted, %d received, %d%% packet loss\n",
send_packet_cnt, recv_packet_cnt, (int)(((send_packet_cnt-recv_packet_cnt) * 100)/send_packet_cnt));
return 0;
}
//quick test
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s [address]\n", argv[0]);
return 0;
}
return ping(argv[1]);
}
结合抓包工具就比较容易理解:
执行:sudo ./ping 114.114.114.114