TCP Ne kadar güvenilir – 1

Bu yazımızda kısaca TCP protokolündeki bazı hususları irdeleyeceğiz.

TCP’yi okulda öğretilenden biraz daha farklı olarak tecrübelerle aktarmayı düşünüyorum. Muhtemelen TCP denince aklımıza reliable kavramı geliyordur. Ben bu detayların aksine günlük hayatta karşımıza çıkabilecek hatta çıkmış olsa bile bu problemi genellikle bir framework kullandığımız için farketmediğimiz durumları ifade etmeye çalışacağım.

Tahmin edileceği gibi bir adet sunucu var, istemci sayısı ise 3 olsun. Uygulamamız ise bir sohbet (chat) uygulaması olsun. Sohbeti gerçekleştiren kişiler biraz agresif karakterli kişiler olsunlar. Öyle kafaları kızınca sohbeti sonlandıran, sizi bloklayan hatta bilgisayarın ağ kablosunu, güç kablosunu çeken hatta bilgisayarı resetleyen cinsten karakterler. Böyle durumlara dayanıklı bir sunucu yazmak kolay değildir, inanın. Bunu her durum için küçük küçük python uygulamalarıyla göstermeye çalışacağım. Ancak ilk olarak teorik bilgi sonra bu durumun canlı uygulamasını sunmaya çalışacağım.

İki muhabbetçinin konuşması:
Muhabbetçiler sunucu aracılığıyla konuşacaklar daima. Yani ilk iletişim kurulduktan sonra sunucu aradan çekilmeyecek. Tasarımı buna göre yapalım. Yani Ali, Veli, Mustafa olsun ve sunucu yazılımının adı da “Lafçı” olsun.

Ali’nin Veli’ye mesaj gönderdiği durumda eğer Ali’nin Lafçı’ya bir TCP oturumu yoksa yeni bir bağlantı açacak ve veriyi bu soketten (bağlantı, oturum) gönderecektir. Lafçı verinin kime iletileceğini veriden çıkaracak ve veriyi eğer kendisine ilgili kullanıcıya ait bir oturum varsa o oturum üzerinden gönderecektir. Aksi halde yani ilgili kullanıcı çevrim içi değil ise ya veritabanına sonra gönderilmek üzere saklayacak veya /dev/null‘a (ne demek istediğimi anladınız*) gönderecektir.

Buraya kadar herşey yolundadır umarım. Şimdi Murphy konuşsun bakalım.

Problem 1:
a) Ali Veli’ye bir mesaj göndermek istiyor.
b) Ali mesajı yazdı ve gönder butonuna bastı.
c) Ali’nin bilgisayarından Lafçı’ya bir TCP oturum kuruldu.
d) Lafçı mesajı aldı, kime göndereceğine baktı ve Veli olduğunu gördü.
e) Veli’nin çevrim içi (online) olduğunu gördü.
f) Lafçı Veli’nin oturumuna (yani istemci soketine) veriyi yazmaya başladı.
g) Lafçı Veli’nin mesajını tam yazamadan Veli programı kapattı

Bu durumda Veli’nin programının kapanması Veli’nin oturumunun kapanmasına (soketin kapanmasına) sebep olur. Yani Lafçı tam mesajı yazamadan (mesela, 100 karakterlik bir mesaj ve 20inci karakteri henüz yazmış ve daha 80 karakter için işlem yapılması gerekirken) Veli soketi kapattı, soru: böyle bir durumda ne olur?

Eğer sunucu uygulamanız yani Lafçı bu durumu daha önceden algılayacak kodu barındırmıyorsa “broken pipe” hatası ile sonlanır. Sonlanır demek bir hafif oldu, aslına bakacak olursanız sunucu uygulamanız kırılır. C’de bu durumdan SIG_PIPE sinyalini bloklayarak kurtulabilirsiniz.

Peki bu hatanın alınması nasıl oldu?
Her iki bilgisayar (yani Lafçı’nın çalıştığı ve Veli’nin kullandığı) soket haberleşmesini TCP stack (yığın) aracılığıyla gerçekeştirmektedir. Lafçı’nın bilgisayarı soketin kapatıldığını Veli’nin bilgisayarındaki TCP stack’in söylemesi ile bilmektedir. Yani Veli’nin bilgisayarındaki program kapanınca Veli’nin bilgisayarındaki TCP stack Lafçı’nın bilgisayarındaki TCP stack’e artık bana yazamazsın demektedir. Karşı TCP stack’ten gelen bu bilgiyi alan Lafçı’nın TCP stack’i ise Lafçı uygulamasına ilgili hata sinyalini göndermektedir. Yani Lafçı arada bir Veli’nin bilgisayarına gidip arkadaş sen soketi kapattın mı dememekte tersine soketi kapatan taraf, arkadaş bak ben kapattım haberin olsun demektedir.

Özetle iki bilgisayar en alt seviyede soket haberleşmesini TCP stack ile yapmaktadır. İki bilgisayardaki soket durumları, paket akışı, window size, ACK almış paketler vb. hepsi TCP stack’lar tarafından kontrol edilmektedir. Bize ise herşeyden arındırılmış temiz bir akış (stream) sunulmaktadır.

Diğer durumlar için ilerleyen yazılarda incelememize devam edeceğiz.

Sağlıcakla kalınız

* /dev/null ile kasıt mesajın discard edileceğidir. /dev/null linux’te bir bir psudo aygıttır. Bu aygıta gönderilen herşey yok olur. Genellikle sistem yöneticileri veya bazı scriptler bazı mesajları baskılamak için mesajı bu aygıta yönlendirirler.

Network Programlama

Giriş:

Yeni bir yazı yazmanın zamanı geçiyordu bile. Bu yazımızda istemci tarafını yazalım. İstemci için daha az bilgiye ihtiyacımız var. Bir önceki yazımızda (Soket Programlama – 2) sunucuya bağlanmak için telnet veya netcat programını kullanmıştık. Bu yazımızda kendi istemcimizle sunucumuza bağlanacağız. Sunucumuzun tek mesajlık gücü vardı hatırlarsanız.

İstemci Yapalım:

Bu işlem için sunucuya bağlanmak için bir sokete ihtiyaç duymaktayız. Soket elde etme işlemi sunucu soketteki gibidir:
import socket
s =socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((”, 9000))
data =s.recv(1024)
print data

Bu kod tahmin edilebileceği gibi bir adet TCP protokolünü kullanacak soket nesnesi oluşturur. “connect” metoduna geçilen 9000 değeri sunucunun hangi porttan bağlantı beklediğidir. Bağlantı sağlandıktan sonra “recv” metodu 1024’lük bir buffer’a veriyi saklayacaktır. Veri elde edildikten sonra ekrana yazdırılacak ve program sonlanacaktır.

Herşey bu kadar basit mi?

Malesef aslında soket programlama göründüğünden biraz daha karışık olabilir. “send” ve “recv” metodları buffered bir okuma ve yazma yapmaktadırlar. Yani “send” ile gönderilen verinin tamamı gönderilmeyebilir. Hani bir TCP stack var demiştik. İşte TCP stack’teki bufferlar dolmadan send işlemi (verinin gerçekten kabloya elektriksel olarak iletilmesi) gerçekleşmemektedir. Benzer şekilde “recv” metodu da buffer dolmadan okumayı sonlandırmamaktadır. Yani siz 1024’lük bir veri okuyacaksınız ancak 100’lük bir veri okuyabilirsiniz.

Biraz daha detaylandıralım. İlk olarak, buffered I/O meselesi biraz uzun hikaye. Kısaca özetleyecek olursak, C’deki gets() fonksiyonu da bu şekilde çalışmaktadır diyebiliriz. Buffer’ın tazelenmesi veya boşaltılmasını bazı özel karakterler (örneğin ‘\n’) tetikleyebilmektedir. Bizim ilk eğitimlerimizdeki örneklerimizde ‘n’ olmamasına rağmen recv nasıl bizim sunucumuzun gönderdiği yazıyı aldı peki? Burada da özel bir durum var. Herhangi bir soketin bağlantısı kapatıldığında (close()) TCP stack’taki bufferlardaki veriler buffer dolmamış olsa bile iletilir. Bizim sunucunun mesajını görebilmemizin sebebi buydu. Bunu gerçekten görmek için ve buffered I/O’nun etkisini görmek için sunucu kodumuzu biraz değiştirelim:
import socket
import time

s =socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((”, 9000))
s.listen(5)
conn, addr =s.accept()

conn.send(‘Merhaba\n’)
for i in range(100):
# dikkat ediniz bu send metodunda \n yok
conn.send(‘Merhaba ‘)

time.sleep(100)

conn.close()
Kodumuzda ilk göndermeyi (send()) ‘\n’ ile yaptıktan sonra, 100 kere daha aynı mesajı sokete yazmaktayız. Bu işlemin ardından 100 saniye daha bekliyoruz ki sokete yazma kendimizce (!) tamamlansın (buffered I/O’nun etkisini göstermek için sleep bulunuyor).

Benzer şekilde istemciyi de düzenleyelim.
import socket

s =socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((”, 9000))

fori in range(100):
data =s.recv(1024)
print data
Sunucu yazılımımızı çalıştırdıktan sonra istemci yazılımımızı çalıştıralım. Evet recv fonksiyonunun farklı büyüklüklerde okuduğunu (Merhaba yazısının aynı satırda farklı sayılarda olduğuna dikkat ediniz) göreceğiz.

Sorular?

1. Bu örneği sunucu yazılımında conn.close() satırını kaldırarak tekrar çalıştırınız. Nasıl bir fark gördünüz, ne bekliyordunuz?

2. Aynı örneği istemcide 100 kere okuma yapmadan sadece tek okuma yaparak tekrarlayınız. Nasıl bir fark gördünüz, ne bekliyordunuz, niçin bu sonucu aldınız?

2. sorunun cevabı biraz birincinin cevabını verdiğinden bu soruyu cevaplayalım. Bir tek “Merhaba” satırı göreceksiniz, çünkü send(“Merhaba\n”) ‘\n’ barındırdığından istemcideki recv tarafından okunur ve recv fonksiyonu istemcide döner (bloklama yapmaz, sonlanır) . Malesef send tarafından diğer Merhaba’lar yazılmış olmasına rağmen istemci sonlandığından bu veriler istemci tarafından hiç işlenmez. Bu daha ilginç bir problemi göz önüne koymaktadır. Eğer istemci sonlanmış ve sunucu send yapmışsa, ki bu durumda kapanmış bir sokete yazma yapmaktadır, BROKEN_PIPE sinyalini sunucu neden almamıştır? Hatta, istemci kodunu soketi kapatacak şekilde aşağıdaki gibi düzeltiniz ve denemeyi tekrar yapınız, bu durumda “Connection reset” uyarısı göreceksiniz. Farkı yaratan sebebi bulmaya çalışınız.

import socket

s =socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((”, 9000))
data =s.recv(1024)
print data
s.close()

Sağlıcakla kalınız

TCP Ne Kadar Güvenilir – 2

Bu yazımızda TCP’nin sıklıkla karşılaşılmayabilecek, ancak kritik bir problemini açıklayacağız. Bu problemi açıklamadan önce send() ve recv() metotlarının aslında TCP stack’i aracı yaparak işlevlerini gördüklerini ifade etmiştik. Bir soket kapandığı zaman kapatmayı tetikleyen yazılımın bulunduğu bilgisayardaki TCP stack’in karşı taraftak TCP stack’a durumu bildirdiğini ifade etmiştik. Bu cümleyi ne kadar tekrarlasak o kadar önemli, çünkü aslında iletişim kuranlar TCP stack’ler. Programlar TCP stack’leri aracı olarak kullanıyorlar. Paketlerdeki ACK’leri, checksum’ları, akış kontrolünü (bu terimleri bilmiyorsanız boşverin) uygulamalar değil TCP stack kontrol etmektedir.

Problemi tanımlayalım

İstemci-sunucu arasında bir bağlantı kurulmuş durumda olduğunu düşünelim. Bir mesajlaşma programı gibi ara ara sohbet iletileri gelip gidiyor.

Sunucuda istemciyle olan bağlantıyı tutan bir istemci soket vardır, (tabiki bir de sunucu soket, ancak bu konumuzla alakalı değil)
İstemcide sunucuyla iletişimi sağlayan bir istemci soket var.
İstemcinin bilgisayarının bağlandığı mekandaki elektrik gitti. Yedek güç kaynağı vb. de yok. Yani bilgisayar kapandı. Bunun doğal sonucu olarak istemcinin TCP stack’i artık ölüdür denilebilir. Kimse sunucunun TCP stack’ine bu durumu bildiremeyecek yani. TCP stack ise recv() fonksiyonu içerisinde olduğunu düşünelim.

Sizce, recv() bir hata ile veya hiç veri okumadan mı dönecek? Böyle bir durumda ne beklersiniz. İlk olarak recv()’in davranışını test edebilirsiniz. Soket Programlama – 2 yazımızdaki sunucu kodunu aşağıdaki gibi değiştirerek recv()’in davranışını gözlemleyelim:

import socket
import time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((”, 9000))
s.listen(5)
conn, addr = s.accept()

time.sleep(100000)

conn.close()

Evet yeterince beklediğinizi düşünüyorsanız her iki programı da kapatabilirsiniz. recv()’in dönmediğini gördünüz. sleep süresini 10 saniye olarak değiştirip tekrar programı çalıştırınız ve recv()’in 10 saniye sonra sonlanacağını göreceksiniz. Bunun sebebi conn.close() metodu ile soketin kapatılması ve TCP stack’in bu durumu istemcinin TCP stack’ine iletmesidir. Yani recv()’in sonlamasının sebebi (bir okuma yapmaya çalışmasına rağmen) sunucunun soketi kapatması ve sunucu TCP stack’in durumu istemciye bildirmesidir.

Yukarıdaki ip uçlarını göz önünde bulundurursak, istemci recv() yaparken sunucu (veya tam tersi de geçerli) elektriksel veya başka bir sebeple kapanırsa ne olur? Cevap sizi şaşırtacak cinsten, recv() bunu algılayamayacak, ve ister inanın ister inanmayın oldukça uzun bir süre sonra recv() dönecektir. Eğer recv() fonksiyonu sunucuda çağrıldığı esnada bu durum gerçekleşirse, sunucu ister bu işlemi ayrı bir thread’de isterse ayrı bir process’de yapsın, kaynaklarını israf etmiş olacaktır. Hatta, bu durumun kendisi bir atak olarak kullanılabilir. Şöyle düşünelim, bir linux işletim sisteminde sunucu, her istemci için ayrı bir thread/process açılıyor olsun. Böyle bir atak ile sunucu servis dışı bırakılabilir.

Sağlıcakla kalınız

TCP Ne Kadar Güvenilir – 3

Bir önceki yazımızda (TCP Ne Kadar Güvenilir – 2) recv() metoduna kafayı takmıştık.

recv() metodu çağrılması esnasında karşı uçtaki cihazın bir sebeple tamamen erişilemez olması durumunda (örneğin elektrik kesilmesi, ağ kablosunun çekilmesi/kopması, cihazın reset alması vb.) recv()’in dönmediğini ifade etmiştik. Bu durumu bir örnekle göstermiştik. Aslında eğer sanallaştırma yapabilme imkanınız varsa (örneğin VirtualBox), kablo bağlantısını koparma gibi özellikleri VirtualBox sunmaktadır. recv()’in kablo bağlantısı koparılsa bile sonlanmadığını bu şekilde de test edebilirsiniz.

Bu yazıda, aynı durumda yani kablo çekilmesi veya reset alınması durumunda, send() durumunu irdeleyeceğiz. Bu seferki durum biraz daha karmaşık.

Continue reading

Sanallaştırma – Virtualbox

Sanallaştırma

Sanallaştırma Bulut Bilişim’in can damarı, yazılımıcının dostu ve öğrenmek için büyük olanaklar veren bir teknolojidir. Sanallaştırma ile fiziksel makinemiz üzerinde değişiklik yapmadan gerçek bir fiziksel makinenin sunduğu hizmetlerin tamamına (hemen hemen) erişilebilmesi mümkündür. Günümüzde Docker gibi teknolojilerle çok daha az yükle bunları başarmak mümkün olsa da sanallaştırma gerçek bir fiziksel makine yerine geçebilen oldukça önemli bir teknolojidir.

Virtualbox, masaüstü sanallaştırması düşünüldüğünde açık kaynak olması, güncellemelerinin sıklıkla yapılması, aralarında Windows’un bulunduğu pekçok işletim sistemi için problemsiz çalışması düşünüldüğünde ilk akla gelen ürünlerdendir. Virtualbox eğer linux dağıtımı kullanıyorsanız paket yöneticisinden, eğer windows işletim sistemi sahibi iseniz virtualbox.org sitesinden indirilip kurulabilir. Kurulum adımlarını yapabildiğinizi varsayarak, virtualbox’ın önemli olduğunu düşündüğümüz farklılıklarını ifade edelim.

Linux Dağıtımları için “dkms”

DKMS (dynamic kernel module support), virtualbox guest additons (virtualbox misafir eklentileri), paketinin işletim sistemi kernel güncellemelerinden sonra kendiliğinden derlenmesi ve kayıt ettirilmesi amacıyla kullanılmaktadır. Virtualbox kurulumunuzdan sonra “kernel” vari mesajlar alıyorsanız dkms paketini yüklemeyi deneyebilirsiniz. Bu probleminiz çözmez ise virtualb0x-dkms adlı bir paket te işinizi çözebilir. Bu paketlerin kurulumundan sonra bilgisayarınızı yeniden başlatmanız gerekebilir.

Misafir Eklentileri (Guest Additions)

Tam bir masaüstü deneyimi sunabilmek için virtualbox misafir eklentileri kurulmalıdır. Bu işlemden sonra entegre fare, kopyala-yapıştır (genellikle metin), ekran kartının çeşitli ek çözünürlükleri gibi gerçekten faydalı özellikler kullanılabilir hale gelecektir.

Birden fazla ekran

Evet virtualbox birden fazla ekranı desteklemektedir. Bu özellikten faydalanmak için misafir eklentilerinin yüklü olduğundan emin olunuz.

Birden fazla CPU

Bu özellik te virtualbox tarafından desteklenmektedir.

Nested Virtualization:

Bu kavram malesef Virtualbox tarafından desteklenmemektedir. Bu kavram ile sanal makinenin kendisi içerisinde sanal makineler oluşturulabilmesi (emulated değil) ifade edilmektedir.

Sanallaştırma – KVM

Kernel Virtual Machine (KVM)

Sanallaştırmayı bu yazımızda biraz daha açalım. Sanallaştırmayı full-virtualization, para-virtualization ve donanım destekli sanallaştırma olarak üç şeklinde değerlendirebiliriz.

1. Full-virtualization

VMWare bu sınıftadır. Temel olarak x86 mimarisi donanım kaynaklarına erişim için ring-0, ring-1, ring-2 ve ring-3 şekinde 4 seviye belirlemiştir. İşletim sistemleri ring-0’da çalışmayı beklemektedirler. ring-0 en yetkili ve bütün kaynaklara erişilebilen bir seviyedir. Kullanıcı programları ise ring-3’te çalışmaktadırlar. Sanallaştırmadaki zorluk ta tam olarak burada çıkmaktadır. İşletim sistemleri ring-0’da çalışacak şekilde tasarlanmışlardır. Halbuki sanallaştırma yazılımı (hipervizör) ring-0 da çalışmaktadır, yani işletim sistemi ring-0 da çalışmamaktadır. Bu durumun üstesinden gelmek için VMWare binary translation kavramını ortaya koymuştur. Bu kavram ile kullanıcı programları doğrudan ilgili olduğu seviye olan ring-3’te çalıştırılırken, işletim sistemi ring-1’de çalıştırılmakta ancak işletim sistemi ring-0’ı gerektiren bir komut icra edeceği zaman aynı etkiye sahip komu dizisi hipervizör tarafından çalıştırılmaktadır. Yani bu kısım emule olarak ifa edilmektedir. VMWare’in bulduğu ve standart olarak kullanılan bu metod full-virtualization olarak değerlendirilmektedir.

Full virtualization ile işletim sistemi hiçbir şekilde sanal bir sunucu olduğunu bilmez. Hipervizör sanal makine için sanal bios, sanal ses kartı ve diğer gerekli donanım kaynaklarını sanal olarak vermektedir. Bu tekniğin en önemli yanı da zaten işletim sisteminde bir değişiklik gerektirmemesidir.

2. Para-virtualization

Bu sanallaştırmada misafir işletim sistemi değiştirilmek durumundadır. Full virtualization’dan farklı olarak misafir işletim sistemi para-virtualized olduğunu bilir ve bu değişikliğe uğratılmıştır. Bütün işletim sistemleri bu şekilde sanallaştırılamaz.

3. Donanım destekli sanallaştırma (Hardware assisted virtualization)

İşletim sistemlerinin ring-0’da çalışması ihtiyacı nedeniyle CPU üreticileri ring-0’ın altında, hipervizörler için bir seviye daha oluşturdular ve bu seviyeye root mode dediler. Böylelikle para-virtualization veya binary translation’daki yavaşlatmanın önüne geçildi. Sanal işletim sistemi ring-0 da yapılması gereken bir komut çalıştırdığında basitçe hipervizör bu çağrıyı yakalayacak ve CPU’ya aktaracaktır.

KVM

KVM daha çok sunucu sanallaştırması için tercih edilmektedir. Her ne kadar masaüstü sanallaştırması için çeşitli çözümler bulunsa da Virtualbox kadar olgun bir masaüstü çözümü sunmamaktadır. Ancak KVM oldukça hızlı, kararlı, pekçok sanal disk formatını destekleyen (virtualbox ve vmware’inkiler dahil), ağ tarafı oldukça güçlü bir hipervizördür. Kurulumu oldukça kolaydır. Ancak sadece linux dağıtımları için kullanılabilmektedir. qemu sistem komutlarıyla diskler üzerinde snapshot’lar alma, klonlama gibi işlemleri yapmamıza olanak veren araçları barındırmaktadır. Basit bir gui’si vardır. Bu gui ile sanal makineler oluşturabilir, disk kalıpları için havuzlar tanımlayabilir, oluşturulan sanal makinelerin özelliklerini ayarlayabilirsiniz.

Sonuç

Windows masaüstü sanallaştırması genellikle MS Office bağımlılığı nedeniyle (malesef bazen şirketin şablonları vb. sebeplerle bu bağımlılık oluşabiliyor) tercih edilmektedir. Ancak, playonlinux gibi linux çözümleri bu bağımlılığı kaldırmıştır. Eğer masaüstü sanallaştırmasına windows için gerçekten ihtiyaç duymuyorsanız KVM’yi kesinlikle kullanmalısınız.

Linux üzerinde IP çakışmasını bulma

IP çakışması olup olmadığını Linux üzerinde kontrol etme

DHCP ile IP numaranızı alıyorsanız bu problem ile karşılaşmayacaksınız, ancak IP numaranızı elle ayarlıyorsanız ve ağ trafiğinizde ilginçlikler gözlemliyorsanız bu kontrolü aşağıdaki komut ile yapabilirsiniz.

arping -I eth0 -c 3

bu komutu çalıştırırken root haklarına sahip olmalısınız. Yani eğer ubuntu temelli bir işletim sistemi kullanıyorsanız ve IP adresiniz 10.1.1.10 ise:

sudo arping -I eth0 -c 3 10.1.1.10

şeklinde kontrol edebilirsiniz.

-I ile network interface -c ile count belirtilmektedir.