21 Nisan 2017 Cuma

libpcap API

Bazı Önemli Metodlar
Şunlar.
pcap_open_live() //find device
pcap_lookupnet()  //get IP and net mask
pcap_dump_open()  //open savefile
pcap_loop()       //capture packets & call &pcap_dump
pcap_stats()      //output capture stats to screen
pcap_dump_close() //close file
pcap_close()      //close device & free memory
Eğer uygulamayı root olarak çalıştırmak istemezsek şöyle yaparız.
sudo setcap cap_net_raw,cap_net_admin = eip myapplication
Ancak bu durumda LD_LIBRARY_PATH devre dışı kalıyor. Dolayısıyla harici kütüphane ile static linklemek gerekir.

Hata Belleği
Bir çok pcap metodu bir hata belleğini parametre olarak istiyor. pcap kütüphanesi hata oluşunca bu bellek alanına hatayı yazıyor. Bunun için uygulamanın en başında bir hata bellek alanı oluşturmamız gerekiyor. 
Örnek
Şöyle yaparız
//the error code buf of libpcap, PCAP_ERRBUF_SIZE = 256
char ebuf[PCAP_ERRBUF_SIZE];
Örnek
Şöyle yaparız. BUFSIZ C kütüphanesinden gelen bir değerdir. cstdio veya stdio.h dosyasında tanımlıdır.
char errbuf[BUFSIZ];
pcap_lookupdev
Örnek
Şöyle yaparız
char ebuf[PCAP_ERRBUF_SIZE];

//grab a device to peak into
char *dev = pcap_lookupdev(ebuf);
if (dev == NULL) {
  //e.g. "no suitable device found"
  ...
}
pcap_findalldevs - Tüm Ağ Arayüzlerini Dolaşma
Ağ arayüzlerini dolaşmak için şu metod kullanılır.
int pcap_findalldevs( pcap_if_t **  alldevsp, char * errbuf )
Bu metod ile arayüz ismini ve IP adresini, NetMask bilgisini alabiliriz.

name alanı
Windows'ta name alanında "\\Device\\NPF_{...}" gibi bir isim görürüz.  pcap_open_live metoduna name alanındaki ismi geçmek gerekir.

Eğer "Network Connections" ekranındaki "Ethernet" ismini kullanmak istiyorsak biraz kod yazmak gerekir. GetAdapterAddresses() metodu ile tüm arayüzler dolaşılır. pAdapter->FriendlyName değeri "Ethernet" olan arayüz bulunup, pAdapter->AdapterName alanı pcap_open_live metoduna geçilir.

description alanı
Windows'ta description alanında "Realtek PCIe GBE Family Controller" gibi bir açıklama görürüz. Bu isim "Device Manager" ekranında görüle ismin aynısı.

addresses alanı
Arayüzün tüm adreslerini alır. Örnekteki IPv4 olan ve netmask değeri bulunan arayüzün ismi, IP adresi ve netmask adresi yazdırılıyor. Kullanılan iptos metodu WinPCap ile geliyor.
// get the devices list
if (pcap_findalldevs(&devList, errbuf) == -1)
{
  fprintf(stderr, "There is a problem with pcap_findalldevs: %s\n", errbuf);
  return -1;
}

/* scan the list for a suitable device to capture from */
for (dev = devList; dev != NULL; dev = dev->next)
{

  pcap_addr_t *dev_addr; //interface address that used by pcap_findalldevs()

// check if the device captureble
for (dev_addr = dev->addresses; dev_addr != NULL; dev_addr = dev_addr->next) {
 if (dev_addr->addr->sa_family == AF_INET && dev_addr->addr && dev_addr->netmask){
  printf("Found %s on address %s with netmask %s\n", dev->name
   ,iptos(((struct sockaddr_in *)dev_addr->addr)->sin_addr.s_addr),
    iptos(((struct sockaddr_in *)dev_addr->netmask)->sin_addr.s_addr));
  
  }
 }
}
devList işimiz bitince pcap_freealldevs ile silinir.
pcap_freealldevs(devList);
pcap_open_live - Ağ arayüzünü Açma
Paket yakalamadan önce ağ arayüzünü açmak gerekir. Bu işlem için şu metod kullanılır
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms,
                       char *ebuf)
device: ağ arayüzünün ismidir.
snaplen: yakalamak istediğimiz en büyük paket boyudur.
promisc: ağ arayüzünü promiscuous moda sokup sokmak istediğimizi belirtir. Bu moda sokarsak bize gelmeyen paketleri de yakalayabiliriz.
to_ms : paket okumak için ne kadar beklememiz gerektiğimi belirtir. Eğer -1 verirsek paket gelse de gelmese de thread'imiz çalışır bu yüzden çok işlemci harcayabilir. Eğer 0 verirsek sadece paket gelince thread'imiz çalışır.
ebuf: eğer çağrı hata döndürürse, hatanın yazıldığı bellek alanıdır. Şöyle kullandım

char errbuf[BUFSIZ];
pcap_t* pcap_handle = pcap_open_live("\\Device\\NPF_{A-B-C-D",BUFSIZ,1,0,errbuf);
Yani snaplen bayağı bir büyüktü. Promiscous moda geçtim. Paket okumak için beklemedim

Alternatif Ağ Arayüzünü Açma
pcap_open_live aslında hazır diğer metodları kullanmayı kolaylaştıran bir wrapper. Daha fazla kontrol elde etmek istersek kendimiz şöyle yapabiliriz.
pcap_t *handler = pcap_create("eth0",errbuff);
pcap_set_snaplen(handler, 2048);  // Set the snapshot length to 2048
pcap_set_promisc(handler, 1); // Turn promiscuous mode on
pcap_set_timeout(handler, 1); // Set the timeout to 1 milliseconds
int status = pcap_activate(handler);
İstersek buffer buffer büyüklüğünü de tanımlayabiliriz. Buffer büyüklüğü şu anlama gelir.
The buffer size to be set by pcap_set_buffer_size() refers to the (ring-)buffer, which stores the already received packages.
Şöyle yaparız.
adhandle = pcap_create("en1", errbuf);
pcap_set_buffer_size(adhandle, 524288);
pcap_activate(adhandle);
Filtre
Bazı paketler filtrelenebilir. Böylece uygulamamıza ulaşmaz. Önce pcap_compile ile filtre string'i derlenir. Hata yoksa sonuç 0 döner. Şöyle yaparız. PCAP_NETMASK_UNKNOWN eğe tanımlı değilse 0XFFFFFF kullanılabilir.
const char *str = "wlan subtype beacon";
struct bpf_program fp;


if((pcap_compile(pkt_handle, &fp, str, 1, PCAP_NETMASK_UNKNOWN)==-1)
{
    pcap_perror(pkt_handle, "Compile");
}
Ben şöyle filtreler kullandım
ether src not B4:B5:A0:53:25:B2
ether dst not B4:B5:A0:53:25:B2

paket yakalama callback
İmzası şöyle
void packethandler( u_char *args, const struct pcap_pkthdr* pkthdr, 
                    const u_char* packet )
Bu callback pcap_dispatch ve pcap_loop metodlarına geçilen callback fonksiyonudur.

pcap_pkthdr alanı içinde paket için timestamp, paketin ağdaki gerçek büyüklüğü, paketin snapshot büyüklüğü kadar kırpılmış büyüklüğü bulunur.
Örnek
Şöyle yaparız
printf("pkthdr->ts: %d.%d\n", pkthdr->ts.tv_sec, pkthdr->ts.tv_usec);
printf("pkthdr->caplen: %d\n", pkthdr->caplen);
printf("pkthdr->len: %d\n", pkthdr->len);
packet alanı yakalanan byte'ları içerir. Bu alan içinde Ethernet'in FCS alanını gelmiyor. Ethernet sürücüsü bu kısmı atıyor. Atmadığı bazı işletim sistemleri de var, ancak ben Windows ve Linux'ta denedim ve attığını gördüm.

pcap_dispatch - paket Yakalama
Metodun imzası şöyle.
int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
pcap_dispatch veya pcap_next ile de paketleri yakalamak mümkünden ancak yöntemlerin birbirlerine göre artı ve eksilerini anlayamadım.

pcap_loop - paket Yakalama
Ben pcap_loop metodunu kullandım. Metodun imzası şöyle.
int pcap_loop(pcap_t *p, int cnt, pcap_handler my_callback, u_char *user) 
Thread içinden şöyle çağırdım.
pcap_loop(handle, -1, callback, this);
Açıklaması şöyle
Finally, we tell pcap to enter its primary execution loop. In this state, pcap waits until it has received however many packets we want, or receive infinitely if we pass -1 to pcap_loop as a 2nd argument. Every time it gets a new packet in, it calls a callback function. The function that it calls can do anything we want. 
- İlk parametre pcap_open_live metodunun döndürdüğü handle.
- İkinci parametre yakalanacak paket sayısı. -1 ile sayı belirtmiyorum. Ben durduruncaya kadar çalışır.
- Üçüncü parametre paket yakalanınca çağrılan callback metodu.
- Dördüncü callback metoduna benim geçmek istediğim herhangi bir veri. this geçerek callback içinden kendi sınıfımı çağırabildim.
Eğer C kullanıyorsam this parametresi yerine şöyle yaparız
static void mycb(u_char *user,const struct pcap_pkthdr *h, const u_char *packet)
{
  ...
  pcap_breakloop((pcap_t*)user);
}


//create capture handler of libpcap
pcap_t *pd = pcap_open_live(dev, 1024, 0, 1000, ebuf);

//start the loop of capture
int pcap_loop_ret = pcap_loop(pd, -1, mycb, (u_char*)pd);
pcap_loop Linux'ta eğer ağ arayüzü kapatılırsa -1 dönerek çıkar. Windows'ta ise çıkmıyor.

pcap_loop () paketleri yakalamak için kendi içinde read_op isimli bir function pointer kullanılır . Bu function pointer platformdan bağımsız olmayı sağlar. Linux'ta libpcap/pcap-linux.c dosyasına gider.  Burada socketten recvmsg () çağrısı ile paketi okur.

pcap_breakloop - Thread'i Durdurma
Döngüden çıkıp thread'i durdurmak için pcak_breakloop metodunu çağırmak gerekiyor.
void terminate_process(int signum)
{
   pcap_breakloop(handle);
   pcap_close(handle);
} 
pcap_sendpacket - Paket Gönderme
Ben pcap_sendpacket'i kullandım. İmzası şöyle.
int pcap_sendpacket  ( pcap_t *  p,  
                       u_char *  buf,  
                       int  size ) 
Çağrı başarılı ise metod 0 döner. Değilse -1 döner. Dolayısıyla şöyle kullanılır.
if (pcap_sendpacket(pkt_handle,...) != 0)
{
  fprintf(stderr,"\nError sending the packet: \n", pcap_geterr(pkt_hanle));
}
Gönderilecek paketin elle hazırlanması gerekiyor. Önce ethernet header doldurulur, sonra IP paketi doldurulur gibi. Gönderilecek verinin Ethernet'in FCS alanını içermesi gerekmiyor. Ethernet sürücüsü kendi hallediyor.

pcap_inject - Paket Gönderme
Çağrı başarılı ise metod gönderilen byte sayısını döner. Değilse -1 döner.

Windows'ta Çalıştırma
1. Önce WinPCap_4_1_2.exe çalıştırılarak bir driver kuruluyor. Bu dosya www.winpcap.org adresinden indirilebilir. Driver NDIS (Network Driver Interface) seviyesinde.

2. Sonra WpdPack_4_1_2.zip ile include dosyaları ve lib'ler geliyor. Bu dosya da www.winpcap.org adresinden indirilebilir.

Windows'ta derlerken pcap_stdinc.h dosyasındaki şu satırı kapatmak zorunda kaldım.
#define inline __inline
Yoksa hata veriyordu.

Hiç yorum yok:

Yorum Gönder