https://github.com/usrbin-sim/pcap_test
pcap을 이용한 네트워크 프로그래밍을 처음 시작할 때 짰던 코드인데, 다시 수정해서 공부할 겸 코드리뷰
프로그램 목적
>> 라이브 패킷 캡처를 이용하여, IPv4와 TCP 패킷에 대한 source, destination IP/MAC/Port, 그리고 데이터가 존재하는 경우 데이터까지 출력해준다.
packet.h - 패킷 구조체 및 출력용 함수
#pragma once
#include <stdint.h>
#include <stdio.h>
#pragma pack(push,1)
typedef struct {
uint8_t dst_MAC[6];
uint8_t src_MAC[6];
uint16_t ether_type;
}Ether;
typedef struct {
uint8_t v_l;
uint8_t tos;
uint16_t total_len;
uint16_t id;
uint16_t flag;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t src_ip;
uint32_t dst_ip;
}IP;
typedef struct {
uint16_t src_port;
uint16_t dst_port;
uint32_t seq;
uint32_t ack;
uint8_t offset_reserved;
uint8_t flags;
uint16_t window;
uint16_t checksum;
uint16_t urgent_ptr;
}TCP;
typedef struct {
Ether eth;
IP ip;
TCP tcp;
}Packet;
#pragma pack(pop)
void usage() {
printf("syntax: pcap_test <interface>\n");
printf("sample: pcap_test wlan0\n");
}
void print_MAC(uint8_t *addr){
printf(" >> %02X:%02X:%02X:%02X:%02X:%02X\n",
addr[0],addr[1],addr[2],addr[3],
addr[4],addr[5]);
}
void print_IP(uint32_t ip){
printf(" >> %d.%d.%d.%d\n", ip&0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF);
}
Ethernet, IP, TCP 헤더 필드는 직접 구글링을 통해 몇바이트인지 확인을 하여 만들었다 각 헤더 필드에 대한 정리도 나중에 따로 정리해서 올려야겠다...
아무튼 Ether, IP, TCP 헤더를 각각 만들고, 이를 한번에 묶어주는 Packet 구조체를 만듬
#pragma pack(push, 1), #pragma pack(pop)을 사용한 이유는 패킷 필드들을 각 크기에 맞게 사용하기 위함이다. 안해주면 자동으로 큰거에 맞춰서 정렬이 되어서 이대로 패킷 포인터를 만들어 사용하면 잘못된 필드를 가리킬 수 있다
Packet * packet = (Packet *)data;
if(ntohs(packet->eth.ether_type) != 2048){ // If not IPv4
continue;
}
uint8_t ip_header_len = (packet->ip.v_l & 0xF) * 4;
uint8_t tcp_header_len = (packet->tcp.offset_reserved >> 4) * 4;
uint16_t ip_len = ntohs(packet->ip.total_len);
if(packet->ip.protocol != 6){ //if not TCP
continue;
}
사용은 pacp_test.cpp의 main 함수 내에서 다음과 같이 사용했다. 라이브로 캡처한 패킷 데이터가 담겨있는 data 포인터를 Packet * 형으로 변환하면 쉽게 Ehter, IP, TCP에 접근할 수 있다.
따라서 두번째에 나오는 if문에서 처럼 packet->eth.ether_type 이런식으로 접근하여 사용할 수 있는 것
Ethernet이나 IP, TCP 필드의 경우에는 헤더를 정의 해 주었으므로 그냥 위처럼 packet 포인터를 통해 바로 접근할 수 있다. 하지만 만약 데이터가 존재하는 경우에는 tcp 헤더가 끝난 이후부터 데이터가 시작되기 때문에 각 헤더들의 길이를 계산해주어서 인덱스를 계산해주어야 한다.
//data
if(ip_len-ip_header_len-tcp_header_len > 0){
real_data = (unsigned char *)(packet + sizeof(Ether) + ip_header_len + tcp_header_len);
printf("data >> \n");
for(int i = 0; i < ip_len-ip_header_len-tcp_header_len; i++){
printf("%02X ", real_data[i]);
if(i % 16 == 0){
printf("\n");
}
}printf("\n");
}
데이터를 출력하는 부분. 더 위에 코드를 보면 ip 패킷 전체 길이와 ip 헤더 길이, tcp 헤더 길이를 구해준 것을 확인할 수 있다. ip 패킷 전체 길이에서 각 헤더 길이를 뺀 값이 0보다 크다면 tcp 헤더 뒤에 데이터가 더 붙어있다는 뜻이므로 이를 확인하여 0보다 크면 데이터를 출력해주도록 함
packet 포인터가 가리키는 데이터에서 ehter 헤더, ip 헤더, tcp 헤더를 제외한 그 이후의 패킷 부터가 데이터이기 때문에 Ether의 크기와 각 헤더의 사이즈를 더하여 인덱스를 뒤로 밀고 해당 위치부터 출력을 하면 TCP 데이터가 출력이 된다
프로그램을 돌린 화면
처음 이 코드를 짤때는 Packet 구조체를 만들 생각을 못해서 그냥 일일히 헤더 길이 더해서, 배열 미루고, 값 새로 저장하고 하면서 되게 복잡하게 코드를 짰었는데, 구조체를 깔끔하게 만들고 나니까 이처럼 간단하게 패킷의 다양한 헤더에 접근할 수 있게 되었다
'Network > Programming' 카테고리의 다른 글
[Scapy] IP scan (0) | 2020.01.26 |
---|---|
[Network programming] Wireshark staitistics 따라하기 (0) | 2020.01.24 |