-------------------------------------------- Libnet ve Libpcap ile Etkili Programlama -------------------------------------------- Herseyden once bu dokumanda butun libnet ve libpcap tamamiyle anlatmayacagim. Bu dokuman, libnet ve libpcap kullanilarak hizli ve kolay programlama icin bir bir kaynak teskil etmesi dogrultusunda niyetlenilmistir. Libnet icin daha detayli bilgiyi http://www.packetfactory.net/libnet/manual/ 'dan elde edebilirsiniz. Ayni sekilde libpcap icin daha detayli bilgileri libpcap icin hazirlanmis man sayfalarindan elde edebilirsiniz. 1. Giris Herhangi bir isletim sisteminde alt seviyede socket program yazmak ilk once o isletim sisteminin o seviyeyi, katmani nasil kullandigina bagli. POSIX standartlarina gore gelistirilmis isletim sistemi cekirdeklerinin her biri paket hazirlamanin en alt seviyesi olan datalink katmanini farkli sekilde kullanmaktadirlar. En alt seviyede paket hazirlayan ve bunu aga gonderen bir program yazdiginiz zaman bu programi diger *NIX isletim isletim sistemlerine oldugu gibi tasinmasi hemen hemen imkansiz oluyor. Bu tasinma problemini iki tane kutuphane kullanarak asabiliriz. Bunlara yukarda bahsettigim libnet ve libpcap. Neden ikisi birlikte de tek tek degil? Libpcap ve libnet birbirleriyle entegre calisan, biri paket gondermek icin digeri de paket yakalamak icin kullanilan kutuphanelerdir. Iki kutuphaneyi de iyi sekilde kullanan bir programci, network'de paket gondermek, alma ve paketler uzerinde islemler yapma konusunda cok rahattir. 2. Libnet Kutuphanesi (paket hazirlama ve gonderme ) Libnet kutuphanesi, C programcilari icin herhangi bir seviyede network paketi hazirlama ve bu paketi belirli bir aygit uzerinden gondermek icin tasarlanmistir. Libnetten once programcilar, isletim sistemi bagilmi alt seviyelerle ugrasmak zorunda kalmislardi.Programcilar, Libnet ile beraber alt seviyenin karmasikligindan kurtulmuslardir. Libnet kutuphanesi iki seviyeli paket hazirlama ve gonderme kullanir. Bunlardan bir tanesi Network seviyesinde, digeri iste bir alt seviye olan datalink seviyesinde. Herhangi bir seviyede paket olusturmak ve gondermek icin bazi farkli yollar izlemek gerekmektedir. Simdi ilk once network seviyeli paket gondermek icin gerekli olan prosedurleri bir ornek ile inceleyelim: int main(int argc, char **argv) { int network, packet_size, c; /* network arayuzumuz, paket buyuklugumuz */ u_long src_ip, dst_ip; /* kaynak ip ve hedef makina ip numaralari */ u_short src_prt, dst_prt; /* kaynak port ve hedef port numaralari */ u_char *cp, *packet; /* paketmiz */ dst_ip = libnet_name_resolve("192.168.0.5", LIBNET_DONT_RESOLVE); /* Paket gondermek icin kullanacagimiz hedef ip adresini libnet_name_resolve ile 32 bit bir degiskene atiyoruz. Ikinci parametre olarak gelen LIBNET_DONT_RESOLVE hostname olarak gelecek olan string degiskeninin hicbir sekilde ip adresini DNS lerden sorgulanmamasi icin. */ dst_prt = 21; /* karsidaki host'un 21. portuna baglanacagiz. */ src_ip = libnet_name_resolve("192.168.0.1", LIBNET_DONT_RESOLVE); /* bizim ip adresimiz. Buradaki LIBNET_DONT_RESOLVE ip icin resolve isleminin yapilmamasi icindir.*/ src_prt = 10009; /* rastgele bir port numarasi seciyoruz. */ packet_size = LIBNET_IP_H + LIBNET_TCP_H; /* TCP paketi atmayi istedigimiz varsayilarak, su an network seviyeli paket gonderme surecinde oldugumuz dusunulurse en alt seviye bizim icin IP seviyesi olacaktir. Yani hicbir zaman paketimizin icine datalink seviyesi icin gerekli olan header buyuklugu eklenmeyecektir. Paketimiz bir TCP paketi olacagi icin paket buyuklugumuz IP ve TCP header buyuklukleri toplamidir. */ /* simdi sira artik libnet kutuphanelerini ilkleme islemine geldi. Ilk once bellekte paket_size buyuklugunde ve packet degiskeninin isaret edecegi bir bellek bolgesi araylayalim */ libnet_init_packet(packet_size, &packet); if(packet == NULL){ libnet_error(LIBNET_ERR_FATAL, "libnet_init_packet hatasi\n"); } /* simdi sira network arayuzunu aciyoruz. */ network = libnet_open_raw_sock(IPPROTO_RAW); if(network == -1){ libnet_error(LIBNET_ERR_FATAL, "arayuz acilamadi\n"); } /* evet simdi sira paketimiz icin seviye seviye header bilgilerinin doldurulmasina geldi. libnet kullanmazken oldugu gibi data tipleriyle ilgili hicbir sorunumuz olmayacaktir. Bizim icin onemli nokta header bilgilerindeki parametre degerleridir. */ libnet_build_ip(LIBNET_TCP_H, /* bir ust protokol icin paket boyutu */ IPTOS_LOWDELAY, /* Servis tipi */ 242, /* IP ID */ 0, 48, /* TTL */ IPPROTO_TCP, /* bir ust protokol */ src_ip, /* kaynak bilgisayar ip adresi */ dst_ip, /* hedef bilgisayar ip adresi */ NULL, /* payload (yok) */ 0, /* payload uzunlugu. */ packet); /* ve paketimizin arabellek adresi */ /* Su an elimizde IP protokol header bilgisi islenmis bir paketimiz var. Sira bu paket icin TCP header bilgilerinin islenmesine geldi. Bu kisimda unutulmamasi gereken, TCP header bilgileri IP header bilgilerinden sonra geldigi, yani header icin paketimizin arabellek adresi paketin baslangic adresinden IP header uzunlugu kadar sonradir */ libnet_build_tcp(src_prt, /* kaynak bilgisayarin port adresi */ dst_prt, /* paketin yollanacagi bilgisayarin port adresi */ 0xa1d95, /* rastgele bir sequence numarasi 0x53, /* acknowledge numarasi */ TH_SYN, /* kontrol flagleri */ 1024, /* window buyuklugu */ 0, /* urgent pointer */ NULL, /* payload (yok) */ 0, /* payload uzunlugu */ packet + LIBNET_IP_H); /* TCP header yazilacak paketmizin arabellek adresi */ /* TCP paketimizi olusturduk. Sira TCP headerde eksik kalan bir kisma geldi. Kontrol Toplami (Checksum). Butun bilgler paketimize yazildiktan sonra artik paketimizin checksumunu alarak bu bilgiyi de pakete ekliyoruz. */ if(libnet_do_checksum(packet, IPPROTO_TCP, LIBNET_TCP_H) == -1){ libnet_error(LIBNET_ERR_FATAL, "libnet_do_checksum hatasi\n"); } /* paketimiz artk eksiksiz. Sira artik paketi postalamaya geldi */ c = libnet_write_ip(network, packet, packet_size); if(c < packet_size) libnet_error(LN_ERR_WARNING, "libnet_write_ip sadece %d byte gonderebildi\n", c); /* artik nerdeyse isimiz bitti. Actigimiz arayuzu de kapatiyoruz */ if(libnet_close_raw_sock(network) == -1) libnet_error(LN_ERR_WARNING, "libnet_close_raw_sock arayuzu kapatamadi\n"); /* son olarak da paketimiz icin ayirdigimiz hafiza bolgesini de serbest birakiyoruz */ libnet_destroy_packet(&packet); return 0; } Yukaridaki gibi bircok sayidaki ornekleri http://www.packetfactory.net/libnet/manual/6.html#s6.1 adresinden bulabilirsiniz. Biraz once bahesettigimiz kodda IP en alt seviye olmak üzre paket gonderme islemi yaptik. Simdi ise bir alt seviye olan datalink seviyesinde paket olusturma ve bunun networke gonderilmesinden soz edecegiz. Datalink seviyesinde paket gonderme islemi daha az libnet fonksiyonuna gereksinim duyarken daha fazla protokol bilgisine ihtiyac duymaktadir. Paket ilkleme ve hafiza uzerinde bu paket icin yer acilma islemleri bir once belirtilen seviye icin ayniyken, paket icin header bilgilerinin olusturulmasinda farklilik olmaktadir. Datalink katmaninda paket olusturma ve bu paketi gonderme islemini bir ornek ile aciklayalim. Yerel aga bir ARP REQUEST paketi atan libnet programi yazalim. /* ilk once daha iyi kullanabilecegimiz bir ARP paket yapisi tasarlayalim */ struct arp_pckt { unsigned char dest_mac[6]; unsigned char src_mac[6]; unsigned short protocol_type; unsigned short hardware_type; unsigned short res_protocol; unsigned char h_addr_len; unsigned char p_addr_len; unsigned short op_code; unsigned char src_addr[6]; unsigned char src_ip[4]; unsigned char dest_addr[6]; unsigned char dest_ip[4]; }; int main(int argc, char **argv) { struct arp_pckt *arp; u_short packet_size = 42; u_char errbuf[256]; unsigned ip_src, ip_dest; struct ether_addr *eth; u_char *packet; struct libnet_link_int *libnet_link_int; char *dev; /* calisacak arayuzumuzun xl0 oldugunu farzedelim */ dev = "xl0"; /* simdi paketimizi ilkliyoruz. onceki ornekte oldugu gibi... */ if(libnet_init_packet(packet_size, &packet) == -1){ fprintf(stderr, "Paket icin yeteri kadar hafiza bolgesi ayrilamiyor\n"); return -1; } /* evet paket icin yeterli bellek bolgesini de ayirdiktan sonra sira datalink seviyesinde kullanacagimiz aygiti acmamiza geldi. IP seviyesindeki soket acma isleminden bagimsiz olarak burada libnet_open_link_interface kutuphane fonksiyonunu kullaniyoruz */ if((libnet_link_int = libnet_open_link_interface(dev, errbuf)) == NULL){ fprintf(stderr, "Datalink seviyesinde %s aygiti acilamiyor: %s\n", dev, errbuf); return -1; } /* artik sira yavas yavas arp paketini olusturmaya geliyor. Arp paketini icin gerekli bilgilerin toplanmasi islemlerini yapmamiz gerekmektedir. Bunlar; kendi ip ve mac adresimizin bulunmasi ARP sorgusu icin hedef ip adresinin kullanilir hale getirilmesi gibi... Ilk olarak IP */ ip_src = libnet_get_ipaddr(libnet_link_int, dev, errbuf); ip_src = htonl(ip_src); /* ip adresimizi bulduk, sirada mac adresimiz var */ eth = libnet_get_hwaddr(libnet_link_int, dev, errbuf); /* su an mac adresimiz eth->ether_addr_octet'de tutulmaktadir */ /* yerel ag'da bulundugunu varsaydigimiz ip adresini hedef ip adres olarak belirledik */ ip_dest = inet_addr("192.168.10.112"); /* ve artik sira arp doldurma isleminde :) */ arp = (struct arp_pckt *)packet; arp->protocol_type = htons(ETHERTYPE_ARP); arp->hardware_type = htons(ARPHRD_ETHER); arp->res_protocol = htons(ETHERTYPE_IP); arp->h_addr_len = 6; arp->p_addr_len = 4; arp->op_code = htons(ARPOP_REQUEST); memset(arp->dest_mac, 0xff, 6); memset(arp->dest_addr, 0x0, 6); memcpy(arp->src_mac, eth->ether_addr_octet, 6); memcpy(arp->src_addr, eth->ether_addr_octet, 6); memcpy(arp->src_ip, (char *)&ip_src, 4); memcpy(arp->dest_ip, (char *)&ip_dest, 4); /* hersey tamam simdi paketi direkt olarak ag'a gonderiyoruz */ if(libnet_write_link_layer(libnet_link_int, dev, (u_char *)packet, packet_size) == -1){ fprintf(stderr, "libnet_write_link_layer hatasi\n"); return -1; } libnet_close_link_interface(libnet_link_int); libnet_destroy_packet(&packet); return 0; } Iste hepsi bu kadar. Libnet kutuphanesini kullanarak ARP REQUEST paketi yolladik. Buna benzer sekilde istedigimiz tipte paketi sistem bagimsiz halde olusturabilir ve network'e gonderebiliriz. Sadece paket başlık bilgilerini doldurmak yeterli olmaktadir. 3. Libpcap Kutuphanesi ( paket alma) Paket hazirlama ve gonderme islemini gerceklestirdikten sonra sira simdi ag'dan paket alip bu paketin bilgilerini degerlendirme islemine geldi. Sistem bagimsiz bir standart ile bu islemleri libpcap kutuphanesinin sagladigi imkanlarla gerceklestirebiliriz. Libpcap'da paket alma islemleri belli bir filtreleme yontemi ile yapılmaktadır. Soyle ki, ag'dan alınacak paketlerin protokolu, port bilgileri, ip bilgileri bu filtrelerde tanimlanip sadece bu kurallara uyan paketler kabul edilir. Boylece, uygulama icin gerekli olmayan paketlerin, program icinde tekrar elden gecirilmesi engellenmis olur. Libnet'ten bahsederken yaptigimiz gibi libpcap'i da orneklerle daha derinlemesine anlatmaya calisalim. int main(int argc, char **argv) { pcap_t *handle; char *dev; /* dinlemeyi yapacagimiz arayuz */ char errbuf[PCAP_ERRBUF_SIZE]; /* herhangi hatayi geri dondurdugumuz string */ struct bpf_program filter; /* derlenmis filtre */ char filter_app[] = "port 21"; /* filtre kuralimiz */ bpf_u_int32 netmask; /* netmask adresimiz */ bpf_u_int32 ip_addr; /* ip adresimiz */ struct pcap_pkthdr header; /* pcap'in bize sagladigi baslik bilgisi */ const u_char *packet; /* ve paketimizin kendisi */ /* eger belirli bir arayuzumuz yoksa mevcut arayuzu otomatik olarak sistemden almamiz gerekmektedir. */ dev = pcap_lookupdev(errbuf); /* artik bu arayuzun netmask ve ip adres gibi network bilgilerini alalim. */ pcap_lookupnet(dev, &ip_addr, &netmask,errbuf); /* ve simdi arayuzumuzu promiscous modda aciyoruz pcap_open_live foksiyonundaki ilk parametre aygit ismi, ikinci parametre ise bir seferde okunacak paket buyuklugu, ucuncu parametre aygitin promiscous modda mi calisacagi ile ilgilidir dorduncu parametre is aygittan okuma ile ilgili sure asimi degeridir. Ve son olarak herhangi bir hata elde edersek hata bilgisini koyacagimiz arabellek. */ handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf); if(!handle){ fprintf(stderr, "Arayuz acilamadi: %s", errbuf); exit(1); } /* belirttigimiz kurallarla filtreyi derliyoruz */ pcap_compile(handle, &filter, filter_app, 0, ip_addr); pcap_setfilter(handle, &filter); /* networkten bu kurallara uyan bir paket aliyoruz */ packet = pcap_next(handle, &header); /* en basit islem olarak aldigimiz paket uzerinde baslik bilgisini yazdiralim */ printf("%d buyuklugunde bir paket alindi\n", header.len); pcap_close(handle); return 0; } Goruldugu uzre, libpcap kutuphanesini kullanarak belli filtrelere gore paket almak belli basli bir kac surec dahilinde olmaktadir. Bunlar kisaca su sekilde ozetlenebilir: 1) Dinleme isleminin yapilacagi network arayuzunun belirlenmesi. Bu iki yolla yapilabilir. Statik olarak arayuzu belirtme suretiyle, digeri de otomatik olarak sistemin bize saglamasiyla. 2) Network arayuzunun bilgilerinin alinmasi. 3) Network arayuzuyle baglanti kurulmasi. 4) Filtrelerde belirttigimiz kurallarin derlenip arayuz ile kurulan baglantiya filtrenin uygulanmasi. 5) Paket alma islemleri 6) Ve son olarak da arayuze yapilan baglantinin kapatilmasi Yukaridaki islemlerden 4 numarali islemi uygulamaz isek, yani arayuze yaptigimiz baglanti icin herhangi kural saglamaz isek, arayuz ag'dan butun paketleri alir. Bir nevi datalink katmanina soket olusturmus oluruz. Yani paketimiz ethernet baslik bilgisinden itibaren olusmaktadir. Bu da bizim bu paketi daha esnek kullanmamizi saglar. Ve daha alt seviyede paket kabul etmemizi de. 4. Sonuc Libnet ve Libpcap'in ag katmanlarinin ikinci seviyesinde islem yapabilme ozelligi programcinin hem tasinabilir hem de daha alt seviyelerde program yazmasini cok kolaylastirmaktadir. Sistemlerin birbirinden farkliliklariyla ugrasmazken, hem de o sistemlerin size saglayabilecegi en esnek paket islemlerini gerceklestirebiliyorsunuz... Halil Demirezen [halil at enderunix.org]