본문 바로가기

Traceroute - 3(완) 본문

개인 프로젝트 공부

Traceroute - 3(완)

Seongjun_You 2024. 7. 14. 17:26
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <chrono>
#include <netdb.h>
#include <time.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;
}

std::string change_to_dm(std::string &ip)
{
    char host[1024];
    char service[20];
    struct sockaddr_in sa;

    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;

    if (inet_pton(AF_INET, ip.c_str(), &sa.sin_addr) <= 0) {
        return "";
    }
    if (getnameinfo((struct sockaddr*)&sa, sizeof(sa), host, sizeof(host), service, sizeof(service), 0) != 0) {
        
    }

    return std::string(host);
}

int main(int argc, char *argv[]) {
    int sockfd;
    struct sockaddr_in dest_addr;
    struct icmphdr icmp_hdr;
    

    //소켓 생성
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }
    // 목적지 주소 설정
    memset(&dest_addr, 0, sizeof(dest_addr));
    std::string dest_ip = argv[1];
    inet_pton(AF_INET, dest_ip.c_str(), &dest_addr.sin_addr.s_addr);  // naver 서버

    struct timeval timeout;
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)) < 0) {
        perror("setsockopt");
        close(sockfd);
        return 1;
    }
    std::cout << "traceroute to " << change_to_dm(dest_ip)  << " (" << dest_ip << ") " << ", 20 hops max, 64 byte packets" << std::endl;
    for(int i = 1; i < 21; i++)
    {
        int ttl = i;
        if (setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) {
            perror("setsockopt");
            close(sockfd);
            return 1;
        }

        memset(&icmp_hdr, 0, sizeof(icmp_hdr));
        icmp_hdr.type = ICMP_ECHO;
        icmp_hdr.un.echo.id = htons(getpid());
        icmp_hdr.un.echo.sequence = htons(i);
        icmp_hdr.checksum = checksum(&icmp_hdr, sizeof(icmp_hdr));

        char packet[64];
        // 패킷에 ICMP 헤더 복사
        memcpy(packet, &icmp_hdr, sizeof(icmp_hdr));
        // ICMP 에코 요청 전송

        // 시간 측정
        std::cout << i;
        for(int j = 0; j < 3; j++)
        {
            auto start = std::chrono::high_resolution_clock::now();
            if (sendto(sockfd, packet, sizeof(icmp_hdr), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) <= 0) {
                perror("sendto");
                return 1;
            }
            
            
            int pid = getpid();

        
            char recv_packet[64];
            struct sockaddr_in recv_addr;
            socklen_t addr_len = sizeof(recv_addr);

            ssize_t bytes_recived;
            while(true)
            {
                bytes_recived = recvfrom(sockfd, recv_packet, sizeof(recv_packet), 0, (struct sockaddr *)&recv_addr,  &addr_len);
                if(i == static_cast<int>(recv_packet[bytes_recived-1]))
                {
                    break;
                }
                if(bytes_recived <= 0)
                {
                    break;
                }
            }
            
            auto end = std::chrono::high_resolution_clock::now();
            std::chrono::duration<double, std::milli> rtt = end - start;
            
            if(bytes_recived <= 0){          
                    std::cout << " *";
            }

            else
            {    
                if(j == 0)
                {
                    std::string recv_ip = inet_ntoa(recv_addr.sin_addr);
                    std::cout << " " << change_to_dm(recv_ip) << " (" <<inet_ntoa(recv_addr.sin_addr) << ") " << rtt.count();
                }
                else{
                    std::cout << " " << rtt.count();
                }
            }
                    
        }
        std::cout<<std::endl;
        sleep(3);
              
    }
    
    close(sockfd);
    return 0;
}

전체코드이다.

먼저 리눅스에서 traceroute를 실행할 때 화면을 보면

 

이런 모습이다.

각 항목별로 도메인이름과 IP 그리고 icmp패킷을 총 세 번씩 보내서 얻은 시간 결과들이 있다.

 

 

 

 

std::string change_to_dm(std::string &ip)
{
    char host[1024];
    char service[20];
    struct sockaddr_in sa;

    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;

    if (inet_pton(AF_INET, ip.c_str(), &sa.sin_addr) <= 0) {
        return "";
    }
    if (getnameinfo((struct sockaddr*)&sa, sizeof(sa), host, sizeof(host), service, sizeof(service), 0) != 0) {
        
    }

    return std::string(host);
}

도메인 네임은 getnameinfo() 함수를 통해 구할 수 있었다.

 

 

struct timeval timeout;
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)) < 0) {
        perror("setsockopt");
        close(sockfd);
        return 1;
    }

혹시 몰라서 넣은 timeout코드이다.

패킷을 수신받을 때 계속 대기상태를 막기 위해 넣어두었다.

 

 

 

 

int ttl = i;
        if (setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) {
            perror("setsockopt");
            close(sockfd);
            return 1;
        }

i는 반목문의 인덱스이다. 1부터 20까지 진행시켜 줄 것이다. 즉 20홉까지 확인을 해준다.

setsockopt()에서 ttl도 설정할 수 있다.

 

 

 

 

auto start = std::chrono::high_resolution_clock::now();

...


auto end = std::chrono::high_resolution_clock::now();

시간 측정함수이다. 측정할 시작과 끝에 넣어준다.

std::chrono::duration<double, std::milli> rtt = end - start;

 

측정이 종료되고 계산을 해준다.

 

 

 

ssize_t bytes_recived;
            while(true)
            {
                bytes_recived = recvfrom(sockfd, recv_packet, sizeof(recv_packet), 0, (struct sockaddr *)&recv_addr,  &addr_len);
                if(i == static_cast<int>(recv_packet[bytes_recived-1]))
                {
                    break;
                }
                if(bytes_recived <= 0)
                {
                    break;
                }
            }

while문으로 icmp회신을 받았는데

가끔 icmp패킷을 세 번 보내면 6개의 패킷을 보내는 곳이 있었다.

그렇게 되면 출력하는 인덱스가 꼬였다.

icmp패킷을 받을 때 끝에 ttl과 같은 숫자가 들어가 있어 이를 통해 구분을 해주었다.

필요 없는 패킷을 버리고 내가 원하는 ttl을 가진 패킷만 가져와준다.

 

 

if(bytes_recived <= 0){          
                    std::cout << " *";
            }
            else
            {    
                if(j == 0)
                {
                    std::string recv_ip = inet_ntoa(recv_addr.sin_addr);
                    std::cout << " " << change_to_dm(recv_ip) << " (" <<inet_ntoa(recv_addr.sin_addr) << ") " << rtt.count();
                }
                else{
                    std::cout << " " << rtt.count();
                }
            }

받은 패킷이 0 이하이면 별을 표시해 주고

아니면 도메인네임과 IP 그리고 시간들을 출력해 준다.

 

 

 

 

 

그렇게 만들어진 결과는

이런 식으로 실제 명령어와 흡사하게 만들었다.

시간 측정에 대한 부분은 아직 정확성이 부족하다.

필요 없는 패킷을 버리는 과정에서 쓸데없는 시간이 소요되는 듯했다.

이는 스레드를 통해 ttl을 바꿔가며 전송을 시키고

받은 데이터는 인덱스를 키값으로 가지는 해쉬알고리즘이용하면 될 것 같다.

 

 

 

이렇게 과거 대학교 때 python으로 만들어보았던 프로그램을 c++로 복습할 겸 만들어 보았다.

 

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

Upbit trading bot - 2  (0) 2024.07.19
Upbit trading bot - 1  (0) 2024.07.15
Traceroute - 2  (0) 2024.07.09
Traceroute - 1  (0) 2024.07.08
TFT Support - 5(완)  (0) 2024.07.05
Comments