본문 바로가기
IT/programming

[C/C++] C언어 icmp, ping 구현

by 어느해겨울 2022. 1. 16.

C언어 icmp, ping 구현

C언어를 이용한 icmp, ping 을 구현하였다.

이따끔 network target의 alive 상태를 체크해야할 일이 있다.

Rest API 요청이라던가 TCP session 개시 라던가 뭔가 network request 상황에서 system time_wait, fin_wait, response block 등 여러 상황에 빠지는 상황을 만들지 않으려면 애초에 안보내는게 제일 깔끔하기 때문이다.

그래서 icmp check를 한 후 정상적인 상황에서만 network 적인 작업을 수행한다.

 

소스코드

 - TIME_LOOP_WAIT는 200ms 간격으로 icmp를 체크하겠단 의미다.

 - COUNT_CHECK_LOOP는 5회 동안 체크하겠단 의미다.

즉, 한번에 응답이 오지 않는 상황(e.g. 서버는 정상이나 부하상태 또는 네트워크 오류 등)에 alive check를 retry 하는 동작을 수행한단 의미이다.

5회 수행 * 200밀리초 간격은 즉 최대 1초까지 기다려 보겠단 것이다.

필요한 만큼 수정해서 사용하자.

별다른 수정 없이 아래 함수를 사용하면 즉시 동작 가능하며 C++ 코드가 있는 건 단순히

boolean type과 string class를 사용하고 싶기 때문이다. C++적인 요소를 치환하면 완벽한 C코드로 동작 가능하다.

#include <stdio.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <resolv.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include <string>

#define TIME_LOOP_WAIT          200000      // 200ms
#define COUNT_CHECK_LOOP        5
#define SIZE_ICMP_PACKET        64


using namespace std;

struct ICMP_PACKET {
    struct icmphdr hdr;
    char msg[SIZE_ICMP_PACKET - sizeof(struct icmphdr)];
} typedef ICMP_PACKET_t;



/* functions */
unsigned short checksum(void* _data, int _length) {
    unsigned short* p_data;
    unsigned short result;
    unsigned int   sum = 0;

    p_data = (unsigned short*)_data;

    for (sum = 0; _length > 1; _length -= 2) {
        sum += *p_data++;
    }

    if( _length == 1 ) {
        sum += *(unsigned char*)p_data;
    }

    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;

    return result;
}

bool icmp(string _address) {
    const int sock_value = 255;

    int socket_fd;
    int num_sequence = 1;
    int pid = getpid();
    int idx;

    struct sockaddr_in r_addr;
    struct hostent *hname;
    struct sockaddr_in addr_ping,*addr;

    ICMP_PACKET_t pckt;

    int size_packet_msg = sizeof(pckt.msg);

    struct protoent *t_proto = NULL;
    socklen_t len;

    t_proto = getprotobyname("ICMP");

    hname = gethostbyname(_address.c_str());

    bzero(&addr_ping, sizeof(addr_ping));

    addr_ping.sin_family = hname->h_addrtype;
    addr_ping.sin_port   = 0;
    addr_ping.sin_addr.s_addr = *(long*)hname->h_addr;

    addr = &addr_ping;

    if( (socket_fd = socket(PF_INET, SOCK_RAW, t_proto->p_proto)) < 0 ) {
        printf("icmp() socket open failed : [%02d] %s\n", errno, strerror(errno));
        return false;
    }

    if( setsockopt(socket_fd, SOL_IP, IP_TTL, &sock_value, sizeof(sock_value)) != 0) {
        printf("icmp() set TTL option failed : [%02d] %s\n", errno, strerror(errno));
        return false;
    }

    if ( fcntl(socket_fd, F_SETFL, O_NONBLOCK) != 0 ) {
        printf("icmp() request nonblocking I/O failed : [%02d] %s\n", errno, strerror(errno));
        return false;
    }

    for( int loop_cnt = 0 ; loop_cnt < COUNT_CHECK_LOOP ; loop_cnt++ ) {
        len = sizeof(r_addr);
        if( recvfrom(socket_fd, &pckt, sizeof(pckt), 0x00, (struct sockaddr*)&r_addr, &len) > 0 ) {

            close(socket_fd);
            return true;
        }

        bzero(&pckt, sizeof(pckt));
        pckt.hdr.type = ICMP_ECHO;
        pckt.hdr.un.echo.id = pid;

        for( idx = 0; idx < size_packet_msg - 1 ; idx++ ) {
            pckt.msg[idx] = idx + '0';
        }

        pckt.msg[idx] = 0;
        pckt.hdr.un.echo.sequence = num_sequence++;
        pckt.hdr.checksum = checksum(&pckt, sizeof(pckt));

        if( sendto(socket_fd, &pckt, sizeof(pckt), 0, (struct sockaddr*)addr, sizeof(*addr)) <= 0 ) {
            printf("icmp() sendto failed : [%02d] %s\n", errno, strerror(errno));
        }

        usleep(TIME_LOOP_WAIT);
    }

    close(socket_fd);
    return false;
}


/* main function */
int main(int _argc, char *_argv[]) {

    // target: google DNS
    if( icmp("8.8.8.8") ) {
        printf("icmp() status : [success]\n");

    } else {
        printf("icmp() status : [failed]\n");
    }

    return 0;
}

 

결과

테스트는 전세상 사람들이 다 알고 있는 google DNS인 8.8.8.8로 해보자. 내가 죽어 백골이 진토되는 순간이 와도 google DNS는 죽지 않고 영원할 것이다.

muabow@muabow:~/dev/cpp_file$ g++ -o icmp icmp.cpp ; sudo ./icmp
icmp() status : [success]

따란 당연하게도 성공.

 

위 코드는 도메인명을 사용할 수 없다.(e.g. http://google.com) 당연하게도 name resolve 를 하는 코드가 없기 때문이다.

힌트는 줬으니 필요하면 domain -> ip address 를 바꾸는 코드만 추가해서 사용하자.

 

끝.

 

 

댓글