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.

19 Nisan 2017 Çarşamba

Posix Threadleri

Giriş
Posix threadlerini kullanmak için şu dosya gerekir
#include <pthread.h>
pthread kütüphanesi ile linklemek için -pthread seçeneği kullanılır. Bu kısım hep karışıyor çünkü
-lpthread şeklinde kullanmaya ve düşünmeye alışkınız. Yani şöyle yaparız.

gcc -Wall -g main.c -pthread -o a.out

gcc man sayfasında şöyle yazıyor.
-pthread Adds support for multithreading with the pthreads library. This option sets flags for both the preprocessor and linker.
getcontext setcontext vs.
POSIX threadlerinden önce getcontextsetcontext gibi metodlar varmış. Ancak bu metodlar artık kullanılmıyorlar.

Thread Sınıfı
Bütün thread'ler şu sınıf tipinden tutulur.
pthread_t thread;
pthread_create metodu
pthread_create metodu yazısına taşıdım.

pthread_attr_setdetachedstate metodu
Bu metod ile thread başlatılırken detached başlaması belirtilir.

Detached Thread
Eğer bir thread detach edilmez ise, yani joinable yaratılırsa, threadin exit kodunun okunması gerekir. Aksi halde resource leak oluşacaktır.

Detached thread yaratmak için iki yöntem var. İlki thread'i direkt detached olarak başlatmak.
Bu iş için thread attribute alanına PTHREAD_CREATE_DETACHED atanıyor.
Örnek:

İkincisi ise pthread_detach ile thread başladıktan sonra ayırmak. Örnek:
pthread_detach( pthread_self() );

pthread_join metodu
pthread_join metodu yazısına taşıdım.

pthread_self metodu
thread içinde kendi kendine erişimi sağlar.

Örnek
Linux'ta pthread_t unsigned int olarak tanımlandığı için şöyle print edilebilir.
printf("%u\n", (unsigned int) pthread_self());
Örnek
Şöyle yaparız.
int main()
{
  pthread_t t = pthread_self();
  std::cout << t << std::endl;
  return 0;
}
Metod C kütüphanesinde tanımlı. Eğer pthread kütüphanesi ile linklemezse çıktı olarak şunu alırız.
0
Eğer pthread ile linklersek
g++ test.c -o test -lpthread; ./test
Çıktı olarak şunu alırız.
139675118393152
Açıklaması şöyle
More or less it is as follows: first, as pthread_self() is implemented in the standard C library, it doesn't need to be linked to be linked against -lpthreads.

Now, pthread_self() uses a global variable, a pointer to a TCB (thread control block), to store the thread information, including the ID (unique within the process).

This pointer is initialized to NULL (0), but Pthreads library (when linked) changes it so it now points to current thread header structure.

That's why you get a 0 when not linking with Pthreads and the actual POSIX thread ID when you do.
Thread ve PID
getpid metodu yazısına taşıdım.

pthread_exit
Eğer bir thread işi bittikten sonra çıkmak isterse pthread_exit metodunu çağırır.
pthread_exit(NULL); şeklinde çağırılabilir. Eğer thred'den çıkmak için pthread_exit() yerine exit() metodunu çağırırsak tüm uygulamanın sonlandığını görürüz.

pthread_cancel
Metodun imzası şöyle
int pthread_cancel(pthread_t thread);
Normalde bir thread cancellation point'lerden birisini çağırırsa iptal olur. Yani thread PTHREAD_CANCEL_DEFERRED ile yatatılmıştır. Thread kodunda pthread_testcancel() çağrısı ile de kendimizde iptal edilip edilmediğimizi test edebiliriz. Ancak eğer istenirse thread asenkron iptal edilir hale de getirilebilir.

pthread_setcancelstate ve pthread_setcanceltype
Sadece pure processing yapan thread'ler için kullanılmalı. pthread_setcanceltype ile thread PTHREAD_CANCEL_ASYNCHRONOUS haline sokulur.

Örnek:
void* myThread(void *)
{
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    //...Do work
}
Daha sonra iptal etmek için :

pthread_t thread;
pthread_attr_t attribute;

pthread_attr_init(&attribute);
pthread_attr_setdetachstate(&attribute, PTHREAD_CREATE_DETACHED);

pthread_create(&thread, &attribute, myThread, NULL);

pthread_cancel(thread);
pthread_mutex_lock ve iptal etme
Burada sorulduğu gibi pthread_mutex_lock() ile beklemekte olan bir thread iptal edilemez.

pthread_cleanup_push ve pthread_cleanup_pop
Eğer bir thread iptal edilirse, iptal işleminin ardından temizleme işlevini görecek metodları belirtmek için kullanılır.

pthread_kill
Bir başka thread'i öldürmek için kullanılır.

pthread_setname
pthread_setname_np metodu yazısına taşıdım.

Thread Önceliği
Thread başladıktan sonra pthread_setschedparam(...) ile threadin önceliği değiştirilebilir.
Örnek:
pthread_t thread;

struct sched_param param;
int sched = SCHED_FIFO;
memset(&param, 0, sizeof(sched_param));

// Now I set priority to max value (sched_get_priority_max() returns that value)
param.sched_priority = sched_get_priority_max();

// Create my thread
pthread_create(&thread, NULL, (void *) &hard_number_crunching, (void *) &structure_passed_to_thread);

// finally I set parameters to thread
pthread_setschedparam(&thread, sched, &param);

Bir başka örnek ise burada. pthread_attr_setschedparam ile thread yaratılırken önceliği veriliyor.

pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);

struct sched_param param;
pthread_attr_getschedparam(&attr, &param);
param.sched_priority = 10;
pthread_attr_setschedparam(&attr, &param);

pthread_t thread;
pthread_create(&thread, &attr, ThreadFunction, NULL);


Thread Local Storage
Konuyu Thread Local Storage yazısına taşıdım.