본문 바로가기

Traceroute - 1 본문

개인 프로젝트 공부

Traceroute - 1

Seongjun_You 2024. 7. 8. 21:46

리눅스의 Traceroute를 직접 구현해 보기로 했다.

 

Traceroute는 ICMP 규약을 통해 통신을한다.

ICMP의 패킷 구조이다.

WireShark로 다시 한번 확인해 볼 예정이다.

 

 

먼저 간단하게 ICMP패킷을 만들어 보내는 코드를 작성해 보았다.

 

 

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>

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

    for (sum = 0; len > 1; len -= 2)
        sum += *buf++;
    if (len == 1)
        sum += *(unsigned char *)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;
    return result;
}

int main() {
    int sockfd;
    struct sockaddr_in dest_addr;
    struct icmphdr icmp_hdr;
    char packet[64];

    //소켓 생성
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    // 목적지 주소 설정
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_addr.s_addr = inet_addr("223.130.200.219");  // naver 서버

    // ICMP 헤더 설정
    memset(&icmp_hdr, 0, sizeof(icmp_hdr));
    icmp_hdr.type = ICMP_ECHO;
    icmp_hdr.un.echo.id = getpid();
    icmp_hdr.un.echo.sequence = 1;
    icmp_hdr.checksum = checksum(&icmp_hdr, sizeof(icmp_hdr));

    // 패킷에 ICMP 헤더 복사
    memcpy(packet, &icmp_hdr, sizeof(icmp_hdr));

    // ICMP 에코 요청 전송
    if (sendto(sockfd, packet, sizeof(icmp_hdr), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) <= 0) {
        perror("sendto");
        return 1;
    }
    int pid = getpid();
    std::cout << "ICMP Echo Request sent to www.naver.com" << std::endl;
    std::cout << "Process ID : " << std::hex << pid << std::endl;

    close(sockfd);
    return 0;
}

include <netinet/ip_icmp.h> 

ICMP헤더 구조체를 사용하기 위한 헤더 파일

 

<sys/socket.h>, <arpa/inet.h>  소켓프로그래밍 및 주소변환을 위해 사용하는 헤더 파일

 

 

 

 

 

// 목적지 주소 설정
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_addr.s_addr = inet_addr("223.130.200.219");  // naver 서버

dest_addr을 0으로 초기화해주고

데이터를 넣어준다.

AF_INET은 IPv4 인터넷 프로토콜을 뜻한다.

inet_addr()을 통해 문자열 형태의 IP주소를 네트워크 바이트 순서로 바꾼다.

이는 통신하기 위한 목적지 주소로 사용할 것이다.

 

 

// ICMP 헤더 설정
    memset(&icmp_hdr, 0, sizeof(icmp_hdr));
    icmp_hdr.type = ICMP_ECHO;
    icmp_hdr.un.echo.id = getpid();
    icmp_hdr.un.echo.sequence = 1;
    icmp_hdr.checksum = checksum(&icmp_hdr, sizeof(icmp_hdr));

ICMP 헤더 구조체를 초기화해준다.

type은 ICMP_ECHO를 넣는데 상수로 8 값을 가진다. Echo를 요청하는 type이다.

헤더 id로 getpid()를 넣는데 나중에 다른 패킷과 비교하기 위해 현재 프로세스 id를 넣어주었다.

시퀀스넘버로 1번을 넣어준다.

checksum은 함수를 통해 반환된 값을 넣는다.

 

 

 

// 패킷에 ICMP 헤더 복사
    memcpy(packet, &icmp_hdr, sizeof(icmp_hdr));

    // ICMP 에코 요청 전송
    if (sendto(sockfd, packet, sizeof(icmp_hdr), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) <= 0) {
        perror("sendto");
        return 1;
    }
    int pid = getpid();
    std::cout << "ICMP Echo Request sent to www.naver.com" << std::endl;
    std::cout << "Process ID : " << std::hex << pid << std::endl;

    close(sockfd);
    return 0;

packet에 icmp헤더 패킷을 복사한다.

 

sendto()의 원형은

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

flags는 일반적으로 0으로 설정한다.

 

 

 

 

마지막 checksum에 대해서 살펴본다.

unsigned short checksum(void *b, int len) {
    unsigned short *buf = (unsigned short *)b;
    unsigned int sum = 0;
    unsigned short result;

    for (sum = 0; len > 1; len -= 2)
        sum += *buf++;
    if (len == 1)
        sum += *(unsigned char *)buf;


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

&icmp_hdr과 길이를 매개변수로 전송했다.

void *b가 icmp_hdr의 주소값을 가지며 구조체 데이터를 가리키게 된다.

이를 타입캐스팅해서 *buf에 넣어준다. short인 이유는 2byte씩만 읽어서 계산을 진행할 것이다.

 

가져온 len에서 2 이상일 때 -2를 하며 반복문을 진행하며

sum에 buf가 가리키는 2byte 데이터를 더해주고 주소값을 2byte 더해준다.

마지막 1byte 남을 경우도 있어 이를 2byte로 타입캐스팅해서 더해준다.

 

 

이렇게 더해진 sum의 크기가 16비트를 초과하는 경우도 발생할 수 있다.

예시로 0xABCDEF가 있는 경우 16비트를 right shift 해준다.

그럼 상위비트 0xAB가 나오는데 이를 하위비트 0xCDEF와 더해준다.

더하고 나서 또 16비트를 초과할 수도 있어 right shift 해준 것을 또 더해준다.

그 후 모든 반전비트 즉 1의 보수값으로 만들면 checksum이 끝난다.

 

실행하면 이런 권한오류가 발생할 수 있는데
sudo 권한으로 실행해 주고


ping 범위를 설정해 준다.

 

 

 

마지막으로 와이어샤크에서 확인한 모습이다.

 

'개인 프로젝트 공부' 카테고리의 다른 글

Traceroute - 3(완)  (0) 2024.07.14
Traceroute - 2  (0) 2024.07.09
TFT Support - 5(완)  (0) 2024.07.05
TFT Support - 4  (0) 2024.07.03
TFT Support - 3  (0) 2024.07.02
Comments