https://github.com/usrbin-sim/pcap_stat
이번엔 C++의 STL 사용법을 익히고, pcap 라이브러리를 이용하여 저장된 .pcap 파일을 불러와서 IP와 맥주소를 기반으로 통계를 내는 프로그램을 만들었습니다.
프로그램 목적
>> 저장된 패킷 안에서 두개의 IP 혹은 MAC 주소끼리의 통신에 대한 통계와, 각각의 IP와 MAC 주소에 대해 Tx/Rx 패킷 개수 및 바이트 수를 출력해주는 프로그램 작성
>> pcap 라이브러리와 STL의 map 사용법을 익히기
사실 이미 와이어샤크에서는 라이브로 패킷을 캡처하며 해당 기능을 사용할 수 있긴 하지만, 공부 겸 해서(과제였지만... ㅋㅋ) 작성 해 보았습니다. 전체 소스코드는 깃허브에 있고, 해당 글은 제 자신이 짠 코드에 대해 기록을... 남기는 것입니다 후후
저는 conversations와 endpoints의 정보를 저장하기 위해 map을 사용했는데요, endpoints의 경우에는 map의 key로 IP주소 혹은 MAC 주소 하나로 사용할 수 있지만, conversations와 같은 경우는 양방향 통신에 대한 통계를 내야하기 때문에 key를 만들 때 IP쌍이나, MAC 쌍으로 만들어야 했습니다.
header.h의 구조체 선언부
typedef struct MAC{
uint8_t mac[6];
bool operator <(const MAC& var) const
{
return memcmp(mac, var.mac, sizeof(mac)) < 0;
}
}MAC;
typedef struct MAC_key{
uint8_t src_mac[6];
uint8_t dst_mac[6];
bool operator <(const MAC_key& var) const
{
if(memcmp(src_mac, var.src_mac, sizeof(src_mac)) != 0){
return memcmp(src_mac, var.src_mac, sizeof(src_mac)) < 0;
}else{
return memcmp(dst_mac, var.dst_mac, sizeof(dst_mac)) < 0;
}
}
}MAC_key;
typedef struct IP_key{
uint32_t src_ip;
uint32_t dst_ip;
bool operator <(const IP_key& var) const
{
if(src_ip != var.src_ip){
return src_ip < var.src_ip;
}else{
return dst_ip < var.dst_ip;
}
}
}IP_key;
typedef struct {
unsigned int Tx_packets;
unsigned int Tx_bytes;
unsigned int Rx_packets;
unsigned int Rx_bytes;
unsigned int total_packets;
unsigned int total_bytes;
} values;
MAC 주소는 48bit 주소체계를 가지므로 IP처럼 uint32_t형에 한번에 값을 담지 못합니다. 따라서 저는 이를 해결하기 위해 6칸짜리 uint8_t 배열을 MAC 구조체로 따로 만들었습니다. MAC_key와 IP_key는 conversations를 위한 key로 각각 src와 dst IP/MAC을 가지고 있습니다. values 구조체는 map에서 value로 사용할 값의 구조체입니다.
그리고 map은 트리 구조를 가지므로, key로 사용되는 값(클래스도!)은 크기 연산이 가능해야 합니다. 작다 연산(<) 하나만 정의를 해놔도 같다, 크다, 작다를 모두 표현할 수 있으므로 key로 사용하기 위해서는 반드시 < 연산자를 만들어 주어야 합니다
header.h의 함수 구현부
프로그램을 구상할 때, 어차피 저장된 pcap 파일을 불러와서 통계를 내는 것이므로, 먼저 conversations map을 모두 만든 뒤에 해당 convertaions에서 endpoints 정보를 꺼내오는 방식으로 endpoints map을 만드는 방식을 사용하고자 했습니다. 사실 이렇게 하면 절대 라이브 패킷 캡처에서는 사용할 수 없다는 단점과... 생각보다 연산이 더 많이 필요하다는 단점이 있습니다 이부분은 차후 수정하도록 하겠습니다
void ip_conversations(map<IP_key, values>&conv, IP_key key, struct pcap_pkthdr* header){
map<IP_key, values>::iterator iter;
iter = conv.find(key);
if(iter == conv.end()){
values val;
val.Rx_bytes = 0;
val.Rx_packets = 0;
val.total_packets = 1;
val.total_bytes = header->caplen;
val.Tx_packets = 1;
val.Tx_bytes = header->caplen;
conv.insert(pair<IP_key, values>(key, val));
}else{
iter->second.Tx_packets++;
iter->second.Tx_bytes += header->caplen;
iter->second.total_packets++;
iter->second.total_bytes += header->caplen;
}
}
어차피 패킷 헤더에 있는 길이 정보만 필요하므로!(개수는 그냥 받으면 1 증가시켜주면 되니까..!) 패킷의 실제 데이터 정보는 말고 헤더정보만 인자로 받도록 했습니다.
key는 main에서 패킷을 잡았을 때, IP 헤더에 있는 src ip와 dst ip를 가지고 구성하도록 했습니다. 이 key가 map에 존재하지 않는다면(iter == conv.end()) 처음 넣어줄 value로 값을 초기화 시키고, insert를 해줍니다!!
그리고 만약 key가 map에 존재한다면? Tx와 total 패킷 정보들을 증가시켜주도록 했습니다. 여기서 Tx만 증가시킨 이유는, 일단 key의 source IP 주소를 기준으로 기록을 모두 한 후, source 주소와 destination 주소를 뒤집은 경우가 map에 존재한다면 그 경우를 Rx로 넣어주도록 했기 때문입니다. 따라서 여기서도 이미 오버헤드가 굉장히... 많이 발생한다는걸 이걸 적다가 깨달았네요...
void join_ip_conversations(map<IP_key, values>&conv){
map<IP_key, values>::iterator iter;
for(iter = conv.begin(); iter != conv.end(); ++iter){
IP_key key;
key.src_ip = iter->first.dst_ip;
key.dst_ip = iter->first.src_ip;
map<IP_key, values>::iterator inner_iter = conv.find(key);
if( inner_iter != conv.end()){
iter->second.Rx_bytes += inner_iter->second.Tx_bytes;
iter->second.Rx_packets += inner_iter->second.Tx_packets;
iter->second.total_bytes += inner_iter->second.total_bytes;
iter->second.total_packets += inner_iter->second.total_packets;
conv.erase(inner_iter);
}
}
}
이게 바로 합쳐주는 코드입니다... key의 src와 dst를 뒤집은 다음에, 그것을 key로 하는 것이 map에 존재한다면, Rx를 증가시켜주도록
MAC에 한해서도 동일하게 진행했습니다.
다음은 endpoints 처리에 관한 함수들입니다.
void convert_conv_to_end(map<uint32_t, values>&end, map<IP_key, values>&conv, uint32_t key, values val){
map<IP_key, values>::iterator inner_iter;
for(inner_iter = conv.begin(); inner_iter != conv.end(); ++inner_iter){
if(key == inner_iter->first.src_ip){
val.Tx_bytes += inner_iter->second.Tx_bytes;
val.Tx_packets += inner_iter->second.Tx_packets;
val.Rx_bytes += inner_iter->second.Rx_bytes;
val.Rx_packets += inner_iter->second.Rx_packets;
val.total_bytes += inner_iter->second.total_bytes;
val.total_packets += inner_iter->second.total_packets;
}
if(key == inner_iter->first.dst_ip){
val.Tx_bytes += inner_iter->second.Rx_bytes;
val.Tx_packets += inner_iter->second.Rx_packets;
val.Rx_bytes += inner_iter->second.Tx_bytes;
val.Rx_packets += inner_iter->second.Tx_packets;
val.total_bytes += inner_iter->second.total_bytes;
val.total_packets += inner_iter->second.total_packets;
}
}
end.insert(pair<uint32_t, values>(key, val));
}
void ip_endpoints(map<uint32_t, values>&end, map<IP_key, values>&conv){
map<IP_key, values>::iterator iter;
for(iter = conv.begin(); iter != conv.end(); ++iter){
uint32_t tkey = iter->first.src_ip;
uint32_t rkey = iter->first.dst_ip;
values tval, rval;
if(end.find(tkey) == end.end()){
tval.Tx_bytes = 0;
tval.Tx_packets = 0;
tval.Rx_bytes = 0;
tval.Rx_packets = 0;
tval.total_bytes = 0;
tval.total_packets = 0;
}
convert_conv_to_end(end, conv, tkey, tval);
if(end.find(rkey) == end.end()){
rval.Tx_bytes = 0;
rval.Tx_packets = 0;
rval.Rx_bytes = 0;
rval.Rx_packets = 0;
rval.total_bytes = 0;
rval.total_packets = 0;
}
convert_conv_to_end(end, conv, rkey,rval);
}
}
지금보니 코드를 상당히 복잡하게 작성했네요... conversations map에서 source 주소와 destination 주소를 각각 따로 저장하고, 그 각각 키에 대해서 convert_conv_to_end 함수를 호출하여 endpoints를 뽑아내도록 했습니다.
혼자 코드리뷰를 다시 하고 나니까 참 난장판인 코드가 아닐 수 없구나... 하는 생각이 듭니다
만
혼자 힘으로 해결해내서 뿌듯했습니다 ㅎ
프로그램을 실행시키면 이런식으로 출력이 됩니다!
시간이 되면... 코드를 다시 이쁘게 정리 좀 해야겠다
'Network > Programming' 카테고리의 다른 글
[Scapy] IP scan (0) | 2020.01.26 |
---|---|
[Network programming] pcap library를 사용한 패킷 정보 출력 (0) | 2020.01.23 |