Bootstrap

linux下 C语言实现 ping 命令的功能,判断网络是否连接

前言

在Linux下,我们经常使用 ping 命令来检查设备和网络之间的连通性。如果 ping 命令能够顺利执行,那么就可以判断当前设备网络通畅。如果提示“无法连接到目标主机”或“请求超时”,则网络连接可能有问题。

PING命令介绍

ping 命令是一个常用的网络诊断工具,它通过发送 ICMP(Internet Control Message Protocol)回应请求回应应答来测试与目标主机之间的网络连接。

ping 命令使用的协议是 ICMP。但需要注意的是,虽然 ping 命令使用的是ICMP协议,但是它并不依赖于任何特定的应用层协议。也就是说,ping 命令不使用 TCP、UDP 等应用层协议。相反,它是直接与 IP 协议(网络层协议)交互的。

另外,ping 命令也可以通过其他协议进行操作,例如 IPSec(Internet Protocol Security)等安全协议。在这种情况下,ping命令会使用这些协议来发送和接收数据包。

Ping的原理是:向指定 IP 发送一定长度的数据包,按照约定,若指定 IP 存在的话,会返回同样大小的数据包,若没有在特定时间返回就是超时,就认为指定IP不存在或者网络未连通。因此,可以在代码中通过发送 ICMP 包的方式来判断网络是否连通。

源码

只需要在代码中调用 NetIsOK() 函数即可,该函数返回 true 即表示网络状态良好,否则表示网络状态不连通。本程序中发送了3个 icmp 包,在实际应用中可以根据需要来更改发包数量。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/time.h>


#ifdef _USE_DNS
#include <netdb.h>
#include <string.h>

#define FIXED_TEST_DOMAIN   "www.baidu.com"
#else
#define FIXED_TEST_IP       "39.156.66.10"
#endif

#define MAX_WAIT_TIME       3       //select接口的最大超时时长,超时则认为不可连通外网.
#define REC_MAX_WAIT_TIME   1       //recvfrom接口的最大超时时长,防止网络不稳定导致的误判.
#define MAX_CNT_PACKETS     3       //总计发送包数量.
#define ICMP_HEADSIZE       8
#define PACKET_SIZE         4096

#define NetStatePrint(fmt, ...)     printf("[NetStatus] "fmt"\n", ##__VA_ARGS__)


struct sockaddr_in dest_addr;
int sockfd;
pid_t pid;
int sendPktSize;

char sendPacket[PACKET_SIZE];  
char recvPacket[PACKET_SIZE];  


static void _CloseSocket(void)
{
    close(sockfd);
    sockfd = 0;
}

static unsigned short cal_chksum(unsigned short *addr, int len)
{
    int nleft = len;
    int sum = 0;
    unsigned short *w = addr;
    unsigned short answer = 0;

    /* 把ICMP报头二进制数据以2字节为单位累加起来 */
    while (nleft > 1) {
        sum += *w++;
        nleft -= 2;
    }

    /* 若ICMP报头为奇数个字节,会剩下最后一字节.把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加 */
    if (1 == nleft) {
        *(unsigned char *)(&answer) = *(unsigned char *)w;  
        sum += answer;
    }

    sum = (sum>>16) + (sum&0xffff);
    sum += (sum>>16);

    return ~sum;
}

static int pack(int pkt_idx, char* sendPkt)
{
    int packSize;
    struct icmp *icmp;
    struct timeval *tval;

    icmp = (struct icmp*)sendPkt;

    icmp->icmp_type = ICMP_ECHO; //设置类型为ICMP请求报文.
    icmp->icmp_code = 0;
    icmp->icmp_cksum = 0;
    icmp->icmp_id = pid; //设置当前进程ID为ICMP标示符
    icmp->icmp_seq = pkt_idx; //设置数据包序号
    /* 将当前时间填充为icmp报文的内容 */
    tval = (struct timeval *)icmp->icmp_data;
    if (0 == gettimeofday(tval, NULL)) {
        //printf("<DBG> sec:%ld,usec:%ld since 1970-01-01 00:00:00 UTC\n", tval->tv_sec, tval->tv_usec);
    }

    packSize = ICMP_HEADSIZE + sizeof(struct timeval); //16 Bytes.
    /* recvfrom接口接收时会进行校验值检查,所以必须更新校验值,否则会导致接收失败 */
    icmp->icmp_cksum = cal_chksum((unsigned short *)icmp, packSize);

    //printf("<DBG> send, icmp_type:%d, icmp_id:%d, icmp_seq:%d, packSize:%d\n", icmp->icmp_type, icmp->icmp_id, icmp->icmp_seq, packSize);

    return packSize;
}

static int unpack(int cur_seq, char* buf, int len)
{
    int ipHeaderLen;
    int recPktSize;
    struct ip *ip;
    struct icmp *icmp;

    ip = (struct ip *)buf;
    ipHeaderLen = ip->ip_hl<<2; //求ip报头长度,即ip报头的长度标志乘4.
    icmp = (struct icmp *)(buf+ipHeaderLen); //越过ip报头,指向ICMP报头.
    recPktSize = len - ipHeaderLen; //ICMP报头及ICMP数据报的总长度.
    if (recPktSize < ICMP_HEADSIZE) {
        return -1;
    }

    //printf("<DBG> rec, icmp_type:%d, icmp_id:%d, icmp_seq:%d, packsize:%d\n", icmp->icmp_type, icmp->icmp_id, icmp->icmp_seq, recPktSize);

    if ((ICMP_ECHOREPLY == icmp->icmp_type) && (pid == icmp->icmp_id) && (cur_seq == icmp->icmp_seq) && (recPktSize == sendPktSize)) {
        return 0;
    } else {
        return -1;
    }

}

static int send_packet(int pktIdx, char* sendPkt)
{
    sendPktSize = pack(pktIdx, sendPkt);

    if (0 > sendto(sockfd, sendPkt, sendPktSize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr))) {        
        NetStatePrint("error: sendto()!");
        return -1;
    }

    return 0;
}

static int recv_packet(int pktIdx, char* recvPkt)
{
    struct timeval currentTm;
    int len;
    int fromlen = sizeof(dest_addr);
    struct timeval timeout;
    fd_set rfds;

    while (1) {
        timeout.tv_sec = MAX_WAIT_TIME;
        timeout.tv_usec = 0;

        FD_ZERO(&rfds);
        FD_SET(sockfd,&rfds);

        select(sockfd+1, &rfds, NULL, NULL, &timeout);

        if (FD_ISSET(sockfd,&rfds)) {
            if (0 > (len=recvfrom(sockfd, recvPkt, PACKET_SIZE, 0, (struct sockaddr *)&dest_addr, (socklen_t *)&fromlen)))
            {
                NetStatePrint("error: recvfrom(),%d,%s", errno, strerror(errno));
                return -2;  
            }

            if (0 == unpack(pktIdx, recvPkt, len)) {
                return 0;
            }
        } else {
            NetStatePrint("error: ICMP Packet rec fail.");
            return -1;
        }
    }
}

bool NetIsOk(void)
{
    unsigned int i;
    int iFlag;
    struct timeval recTimeOut = {REC_MAX_WAIT_TIME, 0};

#ifdef _USE_DNS //如果定义该宏,则可以使用域名进行判断网络连接,例如www.baidu.com
    /* 设置目的地址信息 */
    struct hostent *host = NULL;
    char hostname[32] = {0};

    snprintf(hostname, sizeof(hostname)-1, FIXED_TEST_DOMAIN);
    bzero(&dest_addr, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;

    if (NULL == (host=gethostbyname(hostname))) {
        NetStatePrint("error: gethostbyname()!");
        return false;
    }

/*
    NetStatePrint("h_name:%s", host->h_name);
    char ip_addr[20] = {0};
    if (inet_ntop(AF_INET, host->h_addr_list[0], ip_addr, sizeof(ip_addr)-1) != NULL) {
        NetStatePrint("IP_addr:%s", ip_addr);
    }
*/

    bcopy((char*)host->h_addr, (char*)&dest_addr.sin_addr, host->h_length);
    //printf("network ordered integer addr: %#x\n", dest_addr.sin_addr);
#else //如果不使用域名,则只能用ip地址直接发送icmp包,例如谷歌的地址:8.8.8.8
    if (0 > inet_pton(AF_INET, FIXED_TEST_IP, &dest_addr.sin_addr)) {
        NetStatePrint("error: inet_pton()!");
        return false;
    }
    //printf("network ordered integer addr: %#x\n", dest_addr.sin_addr);
#endif

    /* 创建原始ICMP套接字 */
    if (0 > (sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP))) {
        NetStatePrint("error: socket()!");
        return false;
    }

    /* 将Socket设置为非阻塞模式 */
    if (0 > (iFlag = fcntl(sockfd,F_GETFL,0))) {  
        NetStatePrint("error: fcntl(sockfd,F_GETFL,iFlag)!");
        _CloseSocket();
        return false;
    }
    iFlag |= O_NONBLOCK;
    if (0 > (iFlag = fcntl(sockfd,F_SETFL,iFlag))) {
        NetStatePrint("error: fcntl(sockfd,F_SETFL,iFlag)!");
        _CloseSocket();
        return false;
    }

    /* 设置接收函数recvfrom的超时时间 */
    if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &recTimeOut, sizeof(recTimeOut))) {
        NetStatePrint("error: setsockopt()!");
        _CloseSocket();
        return false;
    }

    pid = getpid();

    for (i=0; i<MAX_CNT_PACKETS; i++) {
        if (0 != send_packet(i, sendPacket)) {
            NetStatePrint("error: send_packet()!");
            _CloseSocket();
            return false;
        }

        if (0 == recv_packet(i, recvPacket)) {
            _CloseSocket();
            return true;
        }
    }

    _CloseSocket();

    return false;
}

;