본문 바로가기

IT/개발

ICMP, Ping을 구현하여 RTT 검사하기(C code)

반응형

ICMP (Internet Control Message Protocol)은 프로토콜 중 하나로 네트워크 상황을 모니터링하는 기본적인 프로토콜입니다. 

 

ICMP는 주로 네트워크 장치 간 통신 문제를 해결하고 네트워크 상태 정보를 전송하는 데 사용됩니다.

 

 

ICMP 메시지의 몇 가지 일반적인 용도는 다음과 같습니다:

 

에코 요청 및 응답 (Ping):

ICMP를 사용하여 호스트 간 연결성을 테스트하고 대상 호스트로 데이터 패킷을 보내고 응답을 확인합니다.

 

목적지 불능 알림:

ICMP 메시지를 사용하여 목적지 호스트 또는 네트워크가 도달 불능 상태인 것을 확인 할 수 있습니다.

 

TTL 초과:

ICMP를 사용하여 IP 패킷이 목적지에 도달하지 못하고 TTL (Time To Live) 값이 0으로 감소할 때 TTL 초과 메시지를 생성합니다. 이것은 패킷이 무한 루프에 빠지는 것을 방지합니다.

 

아래에 간단하게 ICMP ECHO packet을 송수신하여 지연시간을 계산하는 코드를 작성하겠습니다.

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/ip_icmp.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>

#define PACKET_SIZE 32
#define PING_TIMEOUT 1

// 체크섬 계산 함수
unsigned short checksum(void *b, int len)
{
    unsigned short *buf = b;
    unsigned int sum = 0;
    unsigned short result;

// 16비트씩 더하여 합을 계산
    for (sum = 0; len > 1; len -= 2)
        sum += *buf++;

// 패킷 길이가 홀수인 경우 마지막 1바이트를 더함
    if (len == 1)
        sum += *(unsigned char *)buf;

// 캐리 발생 시 캐리를 더함
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;

    return result;
}

int main(int argc, char *argv[])
{
    // 목적지 IP 주소를 입력받음
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <destination IP>\n", argv[0]);
        return -1;
    }

    char *target = argv[1];
    struct sockaddr_in dest_addr;
    int sockfd;

    // 소켓 생성
    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)  
    {
        fprintf(stderr,"socket error\n");
        return -2;
    }

    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = 0;
    // 목적지 IP 주소를 네트워크 바이트 순서로 변환하여 저장
    if (inet_pton(AF_INET, target, &(dest_addr.sin_addr)) <= 0)
    {
        fprintf(stderr,"inet_pton error\n");
        close(sockfd);
        return -3;
    }

    char packet[PACKET_SIZE];
    struct icmphdr *icmp = (struct icmphdr *)packet;
    memset(packet, 0xab, sizeof(packet));

    // ICMP Echo Request 패킷 생성
    icmp->type = ICMP_ECHO;
    icmp->code = 0;
    icmp->un.echo.id = getpid();
    icmp->un.echo.sequence = 0;
    icmp->checksum = 0;
    icmp->checksum = checksum(icmp, sizeof(packet));

    // 패킷 전송    
    if (sendto(sockfd, packet, sizeof(packet), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) == -1)
    {
        fprintf(stderr,"sendto error\n");
        close(sockfd);
        return -4;
    }

    // 응답 수신
    char recv_packet[PACKET_SIZE];
    struct sockaddr_in recv_addr;
    socklen_t recv_len = sizeof(recv_addr);
    int recv_bytes;

    // recvfrom에 대한 타임아웃 설정
    struct timeval timeout;
    timeout.tv_sec = PING_TIMEOUT;
    timeout.tv_usec = 0;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout));

    struct timeval start, end;
    gettimeofday(&start, NULL);

    // 패킷 수신
    if (recv_bytes = recvfrom(sockfd, recv_packet, sizeof(recv_packet), 0, (struct sockaddr *)&recv_addr, &recv_len) == -1)    
    {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            fprintf(stderr,"No response received. Host may not be reachable.\n");
            close(sockfd);
            return -5;
        } else {
            fprintf(stderr,"recvfrom error\n");
            close(sockfd);
            return -5;
        }
    }

    gettimeofday(&end, NULL);
    double rtt = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0;
    fprintf(stderr,"Received from %s: icmp_seq=0 time=%.2f ms\n", target, rtt);

    close(sockfd);
    return 0;
}
반응형