Traceroute - 1 본문
리눅스의 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 |