본문 바로가기

Traceroute - 2 본문

개인 프로젝트 공부

Traceroute - 2

Seongjun_You 2024. 7. 9. 20:28

ICMP 패킷을 보내고 다시 받은 패킷의 src IP를 추출했다.

 

 

#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;
    }

    int ttl = 1;
    if (setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) {
        perror("setsockopt");
        close(sockfd);
        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.192.247");  // 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;

    memset(packet, 0, sizeof(icmp_hdr));
    struct sockaddr_in recv_addr;
    socklen_t addr_len = sizeof(recv_addr);

    int bytes_recived = recvfrom(sockfd, packet, sizeof(icmp_hdr), 0, (struct sockaddr *)&recv_addr,  &addr_len);
    if(bytes_recived <= 0){
        perror("recvfrom");
        close(sockfd);
        return 1;
    }

    struct iphdr *ip_hdr = (struct iphdr *)packet;
    struct icmphdr *recv_icmp_hdr =  (struct icmphdr *)(packet + (ip_hdr->ihl * 4));
    
    std::cout << "Received ICMP packet with type " << (int)recv_icmp_hdr->type << std::endl;
    std::cout << "ICMP Echo Reply received from " << inet_ntoa(recv_addr.sin_addr) << std::endl;
    
    close(sockfd);
    return 0;
}

 

먼저 TTL을 설정을 해주어야 한다.

default가 64이기 때문에 ICMP패킷을 보내면 결과가 돌아오지 않는다.

현재는 1만 보내서 패킷이 수신되는지 확인을 해준다.

 

 

 

int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);

해당 함수를 통해 ttl을 설정했다.

 

 

  • sockfd: 소켓 디스크립터이다.
  • IPPROTO_IP: IP 수준에서 옵션을 설정한다.
  • IP_TTL: 설정할 옵션이 TTL임을 지정한다.
  • &ttl: TTL 값에 대한 포인터를 전달한다.
  • sizeof(ttl): TTL 값의 크기를 전달한다.

 

 

 

memset(packet, 0, sizeof(icmp_hdr));
    struct sockaddr_in recv_addr;
    socklen_t addr_len = sizeof(recv_addr);

    int bytes_recived = recvfrom(sockfd, packet, sizeof(icmp_hdr), 0, (struct sockaddr *)&recv_addr,  &addr_len);
    if(bytes_recived <= 0){
        perror("recvfrom");
        close(sockfd);
        return 1;
    }

패킷을 받는 구문이다.

 

 

ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

recvfrom의 원형이다.

 

  • socket: 데이터를 수신할 소켓 디스크립터. 여기서는 sockfd이다.
  • buffer: 수신된 데이터를 저장할 버퍼. 여기서는 packet이다.
  • length: 수신할 데이터의 최대 크기. 여기서는 sizeof(packet)이다.
  • flags: 수신 동작을 제어하는 플래그. 일반적으로 0을 사용한다.
  • src_addr: 데이터를 보낸 주소를 저장할 구조체. 여기서는 recv_addr이다.
  • addrlen: src_addr 구조체의 크기를 나타내는 변수의 포인터. 여기서는 &addr_len이다.

 

 

 

struct iphdr *ip_hdr = (struct iphdr *)packet;
struct icmphdr *recv_icmp_hdr =  (struct icmphdr *)(packet + (ip_hdr->ihl * 4));

2개의 변수를 만들어준다.

난해할 수도 있는데 먼저 수신받은 packet을 iphdr 구조체로 타입캐스팅해서 넘겨준다.

 

 

iphdr의 구조체는

struct iphdr {
    #if defined(__LITTLE_ENDIAN_BITFIELD)
    unsigned int ihl:4;
    unsigned int version:4;
    #elif defined(__BIG_ENDIAN_BITFIELD)
    unsigned int version:4;
    unsigned int ihl:4;
    #else
    #error  "Please fix <asm/byteorder.h>"
    #endif
    u_int8_t tos;
    u_int16_t tot_len;
    u_int16_t id;
    u_int16_t frag_off;
    u_int8_t ttl;
    u_int8_t protocol;
    u_int16_t check;
    u_int32_t saddr;
    u_int32_t daddr;
    /*The options start here. */
};

 

이렇게 생겼다.

이 형식에 맞게 데이터가 들어가게 된다.

 

ICMP 헤더는 IP헤더의 20byte 이후 데이터를 가져오면 된다.

 

(struct icmphdr *)(packet + (ip_hdr->ihl * 4))

ip_hdr->ih1은 IP헤더의 DWORD 단위로 알려준다.

더블 워드는 32bit이다. 32bit는 4byte이므로 4를 곱해준다.

그리고 packet시작 주소와 함께 더해주면 그 주소가 ICMP 헤더의 시작 주소이다.

 

 

 

TTL을 1로 설정하고 실행을 해보면은 

192.168.73.2에 도달한다는 것을 알 수 있다.

이게 내 가상머신의 게이트웨이이다.

 

 

 

 

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

Upbit trading bot - 1  (0) 2024.07.15
Traceroute - 3(완)  (0) 2024.07.14
Traceroute - 1  (0) 2024.07.08
TFT Support - 5(완)  (0) 2024.07.05
TFT Support - 4  (0) 2024.07.03
Comments