본문으로 바로가기

개요

와이어 샤크의 기능중 Statistics>endpoints 에 들어가면 다음과같이 endpoints에 대한 내용을 알려주는 기능이 존재한다.

이와같은 분석 프로그램을 C++을 이용해서 구현해 볼것이다.

Ethernet,IPv4의 endpoint를 분석한 내용

 

 

코드 구현

순서

Step1: pcap 프로그램 작성

Step2: IP,Ethernet 구조체 구성

Step3: map활용 코드 작성

Step4: 출력 코드작성

 

 

[ STEP 0 ] - include

#include <stdio.h>      
#include <string.h>     // memcpy 사용
#include <pcap.h>       // pcap 활용
#include <map>          // STL map 사용 
#include <netinet/ip.h> // ntoh 사용
#include "header.h"     // ip, ethernet 구조체 설정

 

[ STEP 1 ] - pcap 프로그램 작성

char errbuf[PCAP_ERRBUF_SIZE];

pcap_t* handle = pcap_open_offline("/home/ubuntu/STL/test.pcap", errbuf);

if (handle == NULL) {

	fprintf(stderr, "couldn't open file %s: %s\n", "/home/ubuntu/STL/test.pcap", errbuf);
	return -1;
	}
    
struct pcap_pkthdr* header;
const u_char* data;
    
while(handle != NULL){

	int res = pcap_next_ex(handle, &header, &data);
	if (res == 0) continue;
	if (res == -2) break;
}

 

-errbuf: 에러를 담을 버퍼생성

 

-pcap_open_offline: 기존의 pcap파일을 불러올때 사용한다.

 (실시간 패킷을 잡으려면 pacap_open_live를 사용한다.)

 

-struct pcap_pkthdr* header: pcap 헤더 구조체 선언 (include pcap에서 가져온다)

 

-const u_char* data: 패킷 데이터를 담을 data선언

 

-pcap_next_ex:패킷을 하나씩 읽어오는 역할을 한다.

  • 패킷을 문제없이 읽은 경우 1을 반환
  • 시간 초과가 만료 된 경우 0을 반환
  • 패킷을 읽는 동안 오류가 발생하면 -1을 반환
  • 패킷이 EOF면 -2을 반환

[ STEP 2 ] - IP,Ethernet 구조체 구성

struct ETHER{
    uint8_t dst_MAC[6];
    uint8_t src_MAC[6];
    uint16_t ether_type;
};

struct IP{
    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;
};

위와 같이 구성한 이유를 알기 위해서는 패킷 구조를 알아야하는데 모른다면 여기서 확인해보자

2020/06/26 - [네트워크] - network패킷 구조

 

간단히 설명하자면 ETHER 구조체는 MAC의 주소와 TYPE을 얻기 위해 위와 같은 구조체로 설정하였고 

IP 구조체는 다음과 같이 구성되어있어 위와같은 구조체로 설정했다.

 

struct ETHER * mac_key = (struct ETHER *)data;
struct IP * ip_key = (struct IP *)(data+14);   //ether header => 14

따라서 mac의 주소와 ip의 주소를 가져오기위해 다음과 같이 구조체 변수를 선언해줬다.

IP에서 +14를 해준 이유는 이더넷 해더의 14바이트뒤의 값을 가져오기위해 연산을 해줬다.

 

사용방법은 다음과 같이 가져올 수 있다.

//ex
ip_key->dst_ip
ip_key->src_ip
mac_key->des_MAC

 

+inet 활용+

필자는 구조체를 직접 선언해줬는데 libpcap을 사용하면 편하게 사용할수도 있다. netinet에는 네트워크 구조체들이 미리 선언되어있어 가져다 쓰기만 하면 되는데 밑의 코드와 참고 사이트를 활용하면 이해가 더 쉬울것이다.

#include <netinet/in.h>
#include <netinet/ip.h>

~
pcap_offline등 추가 코드
~

struct ip * IPheader;

IPheader = (ip*)(date+sizeof(struct ether_header));

IPheader->ip_src 
IPheader->ip_dst

참고: screwsliding.tistory.com/entry/Pcap-IP

 

[ STEP 3 ] - map활용 코드 작성

이제 map을 활용해서 dst와 src를 잘 정리하는 과정이 필요하다.

STL map을 잘 모른다면 아래의 글을 먼저 읽고 와도 좋다.
2021/01/22 - [c++/STL] - [STL]MAP(기본 개념 및 예시)
2021/01/23 - [c++/STL] - [STL]MAP(구조체 활용)

우선 기본적으로 map의 key는 주소value는 packet수 나 byte수를 담는 구조로 설정할것이다.

 

구조체 VALUES의 구성은 다음과 같다.

struct VALUES{
    unsigned int Tx_packets;
    unsigned int Tx_bytes;
    unsigned int Rx_packets;
    unsigned int Rx_bytes;

};

 

IP endpoint

map<uint32_t,VALUES> ip_;

위와같이 map을 선언해준다. 

 

이어서 다음 코드는 map에 각 ip별로 rx,tx,packet수를 연산해주는 코드이다.

if (ip_.find(ip_key->dst_ip) == ip_.end()){
	VALUES val;

	val.Tx_bytes=0;
	val.Rx_bytes=header->len;
	val.Tx_packets=0;
	val.Rx_packets=1;

	ip_.insert(pair<uint32_t,VALUES>((ip_key->dst_ip),val));
}
else{
	ip_[ip_key->dst_ip].Rx_packets  += 1;
	ip_[ip_key->dst_ip].Rx_bytes += header->len;
}

if (ip_.find(ip_key->src_ip) == ip_.end()){
	VALUES val;

	val.Tx_bytes=header->len;
	val.Rx_bytes=0;
	val.Tx_packets=1;
	val.Rx_packets=0;

	ip_.insert(pair<uint32_t,VALUES>((ip_key->src_ip),val));
}
else{
	ip_[ip_key->src_ip].Tx_packets  += 1;
	ip_[ip_key->src_ip].Tx_bytes += header->len;
}

 만약 ip가 존재하지 않다면 새롭게 insert를 사용하여 넣어주고 만약 존재한다면 기존 값에 +해주는 방식이다.

 

 

Ethernet endpoint

mac 주소는 uint32_t 값에 한번에 안담기기때문에 key로 구조체안의 배열을 사용해서 구해줬다.

struct MAC{
    uint8_t MAC_a[6];
    bool operator <(const MAC &var) const
    {
        return memcmp(MAC_a, var.MAC_a, sizeof(MAC)) < 0;
    }
};
여기서 연산자 오버라이딩 한이유는 map은 이진트리여서 key값에 구조체가오면 오버라이딩을 통해 탐색할수 있게 만들어줘야하기 때문이다.

 

map<MAC,VALUES> mac_;

이어서 다음과 같이 map을 선언한뒤

MAC mac_a_r,mac_a_t;

memcpy(mac_a_r.MAC_a,mac_key->dst_MAC,sizeof(mac_a_r));
memcpy(mac_a_t.MAC_a,mac_key->src_MAC,sizeof(mac_a_t));

구조체 변수를 생성해 배열이니 memcpy를 통해 값을 넣어주고

 

ip와 동일하게 연산해준다.

if (mac_.find(mac_a_r) == mac_.end()){
	VALUES val;

 	val.Tx_bytes=0;
	val.Rx_bytes=header->len;
	val.Tx_packets=0;
	val.Rx_packets=1;

	mac_.insert(pair<MAC,VALUES>((mac_a_r),val));

}
else{
	mac_[mac_a_r].Rx_packets  += 1;
	mac_[mac_a_r].Rx_bytes += header->len;
}
if (mac_.find(mac_a_t) == mac_.end()){
	VALUES val;

	val.Tx_bytes=header->len;
	val.Rx_bytes=0;
	val.Tx_packets=1;
	val.Rx_packets=0;

	mac_.insert(pair<MAC,VALUES>((mac_a_t),val));

}
else{
	mac_[mac_a_t].Tx_packets  += 1;
	mac_[mac_a_t].Tx_bytes += header->len;
}

 

 

[ STEP 4 ] - 출력 코드작성

 

IP endpoint

void ntoa(uint32_t ip, char * dst){
    sprintf(dst, "%d.%d.%d.%d", ip&0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF);
}

위 코드는 data의 ip들은 정수값으로 표현되는데 우리가 알고있는 ip모양으로 변환시키는 함수이다.

 

 

    //print ip endpoint
    printf("<IPv4 Endpoints>\n");
    printf("------------------------------------------------------------------------------------\n");
    printf("|     Address      |   Tx Packets  |    Tx Bytes   |   Rx Packets  |    Rx Bytes   |\n");
    printf("------------------------------------------------------------------------------------\n");
    map<uint32_t, VALUES>::iterator iter;
    for(iter = ip_.begin(); iter != ip_.end(); ++iter){
        char addr[18];
        ntoa(iter->first, addr);

        printf("|%18s|%15d|%15d|%15d|%15d|\n", addr,
         iter->second.Tx_packets,iter->second.Tx_bytes, iter->second.Rx_packets, iter->second.Rx_bytes);
        printf("------------------------------------------------------------------------------------\n");
    }

결론적으로 차례대로 출력을 위해 iterator을 이용해서 다음과 같이 출력 시켰다.

 

 

Ethernet endpoint

    //print mac endpoint
    printf("<Ethernet Endpoints>\n");
    printf("------------------------------------------------------------------------------------\n");
    printf("|     Address      |   Tx Packets  |    Tx Bytes   |   Rx Packets  |    Rx Bytes   |\n");
    printf("------------------------------------------------------------------------------------\n");
    map<MAC, VALUES>::iterator iter_;
    for(iter_ = mac_.begin(); iter_ != mac_.end(); ++iter_){



        //because array
        printf("|%02X:%02X:%02X:%02X:%02X:%02X |%15d|%15d|%15d|%15d|\n",
               iter_->first.MAC_a[0],
               iter_->first.MAC_a[1],
               iter_->first.MAC_a[2],
               iter_->first.MAC_a[3],
               iter_->first.MAC_a[4],
               iter_->first.MAC_a[5],
         iter_->second.Tx_packets,iter_->second.Tx_bytes, iter_->second.Rx_packets, iter_->second.Rx_bytes);
        printf("------------------------------------------------------------------------------------\n");
    }

mac 주소는 우리가 배열에 저장을 해뒀음으로 정수인 mac주소를 16진수로 출력시키기 위해 위와같이 코드를 짰다.

 

출력 모습

출력 결과

 

전체 소스코드

github 주소:github.com/wpgur/packet-state

 

wpgur/packet-state

Contribute to wpgur/packet-state development by creating an account on GitHub.

github.com

 

'c++ > 네트워크 프로그래밍' 카테고리의 다른 글

deauth-attack 구현  (0) 2021.02.13