3

Linux下ping的C语言实现

 1 year ago
source link: https://xugaoxiang.com/2023/06/14/linux-ping-in-c-programming-language/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
  • ubuntu 18.04 64bit
  • gcc 4.8.5

ping 命令是一种常用的网络工具,用于测试网络连接的质量和稳定性。它通过向目标主机发送 ICMP (Internet Control Message Protocol) 数据包,并等待目标主机的响应来检测网络连接的状态。

ping 命令发送 ICMP 数据包时,它会记录发送时间,并在接收到目标主机的响应后计算往返时间 (RTT)。如果目标主机没有响应,或者响应时间超过了一定的阈值,ping 命令会认网络连接存在问题。

ping 命令通常是使用 C 语言来编写实现。它使用原始套接字 (raw socket) 来发送和接收 ICMP 数据包。原始套接字是一种特殊的套接字类型,它可以直接访问网络层协议,而不需要经过传输层协议 (如 TCPUDP)的处理。这就使得 ping 命令可以直接发送和接收 ICMP 数据包,而不需要考虑传输层协议的复杂性。

下面是一个简单的 ping 命令的代码示例,使用 C 语言编写,可以在 Linux 系统上编译运行

// ping.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PACKET_SIZE 64
#define MAX_WAIT_TIME 5
#define MAX_NO_PACKETS 3

int sockfd;
char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
struct sockaddr_in dest_addr;
struct sockaddr_in from_addr;
struct timeval tvrecv;
pid_t pid;
int nsend = 0;
int nreceived = 0;
double total_time = 0;
double min_time = 0;
double max_time = 0;

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;
}

void send_packet()
{
    int packsize;
    struct icmp *icmp;
    icmp = (struct icmp *)sendpacket;
    icmp->icmp_type = ICMP_ECHO;
    icmp->icmp_code = 0;
    icmp->icmp_cksum = 0;
    icmp->icmp_id = pid;
    icmp->icmp_seq = nsend++;
    memset(icmp->icmp_data, 0xa5, PACKET_SIZE - 8);
    gettimeofday((struct timeval *)icmp->icmp_data, NULL);
    packsize = 8 + PACKET_SIZE;
    icmp->icmp_cksum = cal_chksum((unsigned short *)icmp, packsize);
    sendto(sockfd, sendpacket, packsize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
}

void recv_packet()
{
    int n;
    socklen_t fromlen;
    fromlen = sizeof(from_addr);
    if ((n = recvfrom(sockfd, recvpacket, sizeof(recvpacket), 0, (struct sockaddr *)&from_addr, &fromlen)) < 0) {
        perror("recvfrom error");
        return;
    }
    gettimeofday(&tvrecv, NULL);
    struct ip *ip = (struct ip *)recvpacket;
    int ip_header_len = ip->ip_hl << 2;
    struct icmp *icmp = (struct icmp *)(recvpacket + ip_header_len);
    int icmp_len = n - ip_header_len;
    if (icmp->icmp_type == ICMP_ECHOREPLY && icmp->icmp_id == pid) {
        nreceived++;
        double rtt = (tvrecv.tv_sec - ((struct timeval *)icmp->icmp_data)->tv_sec) * 1000.0 + (tvrecv.tv_usec - ((struct timeval *)icmp->icmp_data)->tv_usec) / 1000.0;
        total_time += rtt;
        if (rtt < min_time || min_time == 0) {
            min_time = rtt;
        }
        if (rtt > max_time) {
            max_time = rtt;
        }
        printf("%d bytes from %s: icmp_seq=%u ttl=%d time=%.1f ms\n", icmp_len, inet_ntoa(from_addr.sin_addr), icmp->icmp_seq, ip->ip_ttl, rtt);
    }
}

void ping(char *host_name)
{
    struct hostent *host;
    printf("hostname=%s\n", host_name);
    int size = 50 * 1024;
    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
        perror("socket error");
        return;
    }
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
    bzero(&dest_addr, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    if (inet_addr(host_name) == INADDR_NONE) {
        if ((host = gethostbyname(host_name)) == NULL) {
            perror("gethostbyname error");
            return;
        }
        memcpy((char *)&dest_addr.sin_addr, host->h_addr, host->h_length);
    } else {
        dest_addr.sin_addr.s_addr = inet_addr(host_name);
    }
    pid = getpid();
    printf("PING %s (%s) %d bytes of data.\n", host_name, inet_ntoa(dest_addr.sin_addr), PACKET_SIZE);
    while (nsend < MAX_NO_PACKETS) {
        send_packet();
        recv_packet();
        sleep(1);
    }
    printf("%d packets transmitted, %d received, %.1f%% packet loss, time %.1fms\n", nsend, nreceived, (nsend - nreceived) * 100.0 / nsend, total_time);
    printf("rtt min/avg/max = %.1f/%.1f/%.1f ms\n", min_time, total_time / nreceived, max_time);
}

int main(int argc, char *argv[])
{
    char host_name[256];
    if (argc < 2) {
        printf("usage: %s hostname\n", argv[0]);
        return 0;
    }

    strcpy(host_name, argv[1]);
    ping(host_name);
    return 0;
}

接着就来编译,为了和系统自带的 ping 命令区分开来,给可执行文件换个名字

gcc -o pingDemo ping.c 

然后运行一下,这里需要 root 权限

su
./pingDemo 192.168.1.1
linux ping code

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK