C++11 Sağ değer referansları (R-value references) – 1

Sağ değer referansları C++11 standartlarıyla beraber gelen özelliklerden biridir. Temel olarak 2 faydayı amaçlamıştır:

1-Değer Kaydırma (Move Semantics)

2-Perfect Forwarding

Yazının ilerleyen kısımlarında bu konulara değineceğiz. Onun öncesinde ise sol değer, sağ değer ne demek dilerseniz önce ona bir göz atalım.

Bu kavramlar C dilinden miras alınmıştır. Kısaca tanımlarsak sol değer atama operatörünün sol kısmında bulunabilen değerlerdir. Sağ değer ise atama operatörünün sadece sağ kısmında bulunabilen değerlerdir. Örneğin;


int a;

a = 5;  // a bir sol değerdir.

int *b  = &a  // a bir sol değerdir operatör & argüman olarak sol değere ihtiyaç duyar.İfade geçerlidir.

string & foo(); // şeklinde string & döndüren bir fonksiyon olsun

foo() = "islam"; // foo nun geri dönüş değeri sol değer referansı olduğundan ifade geçerlidir.

string func();

func() = "islam"; //burda func'ın geri dönüş değeri geçici bir string nesnesidir bu nesneler ise sağ değerdir. Bunlara atama yapılamaz

a + 5 = 19 // burda da a + 5 ifadesi bir sağ değerdir. Çünkü + operatörünün geri dönüş değeri bu toplamın soınucudur(bu örnekte 10).

++a = 15 // ifade geçerlidir. Çünkü ++ operatörü geri dönüş değeri olarak a nesnesinin kendisini döndürür.Dolayısıyla atama geçerlidir.

Başka bir tanıma göre bellekte bir yer tahsis edilmiş değerlere veya değişkenlere sol değer, bellekte bir yer tahsis edilmemiş veya ismi  (identifier) olmayan değerlere de sağ değer denir.

Sağ değer Referansları

T türünden bir nesnenin (sol değer) referansını T& şeklinde gösteriyorduk, sağ değer referansını ise T&& şeklinde ifade edeceğiz.

Örneğin;


void foo(string &s)
{
cout << "sol değer referansı" << endl;
};

void foo(string &&s)
{
cout << "sağ değer referansı" << endl;
};

int main()
{
string s("cpp turkey user group");

foo(s); // sol değer referansı
foo(string("islam")); // sağ değer referansı
}

Aslında sağ değer referansları const referanslar olduğundan her ikisini birden yakalamak istiyorsak;

void foo(string const &s)
 {
 cout << "sağ-sol değer referansı" << endl;
 }

gibi bir fonksiyon implemente edersek her iki referans türünüde yakalacaktır. Fakat sağ değer referansını ayırt edemeyiz.

Değer Kaydırma (Move Semantics)

C++’ta şimdiye kadar nesnelerin atanmasında dilin tasarımı gereği bir nesnenin değeri diğer nesnenin bellekteki alanına kopyalanıyordu. Değer kaydırmadaki mantıkta ise bu değer kopyalanmıyor, nesne bu değerin bulunduğu bellek alanını gösteriyor. Böylelikle  kopyalama gibi maliyeti yüksek bir işlem yapılmadığından ciddi bir performans kazanımı oluyor.

Örneğin; Bir vector<string> dizinin başka bir diziye kopyalama maliyetini düşünün ctor ve dtor işlevlerinin maliyetini de kattığınızda devasa bir hesap ödemeniz gerekmektedir. Bu konuya devam edeceğiz.

Advertisements

Man In the Middle – 2

Örneğin şöyle bir senaryoda:

Orjinal CH içinde çok sayıda cipher suite var, ancak bizdeki OpenSSL yeni bir CH üretirken daha az cipher suite ekledi ve daha küçük bir paket oluştu diyelim. Buna cevap olarak server’dan çıkan SH, içinde önceki paketin ack değeri seq+payload_len = 181 olarak geldi bize. Bu durumda network içinde normalde sorunsuz giden ack/seq değerlerini modifiye etmeden gönderdiğimizde, 200 byte gönderen client ack içinde 181 görünce önceki paketin gitmediğini zannedip aynı paketi tekrar gönderecek ve bir türlü ilerleyemeyecek.

Ya da tam tersi durumda:

Biz arada raw socket üzerinden dinlediğimiz için her türlü geri dönen SH paketini göreceğiz. Ama tcp header’ı yeniden yazmazsak 201 bekleyen client büyük ihtimalle paketi discard edecektir. Bu tür bir durumu örneğin wireshark’ta incelediğimizde “ACK’ed unseen segment” şeklinde bir hata görünür.

Bunlara ek olarak, paketin içeriği değiştiği için her iki durumda da tcp ve ip checksum değerleri invalid olacak. Bunların da tcp ile ilgili değişiklikler bittikten sonra en son olarak yeniden hesaplanması lazım. Paket boyutunun değiştiği durumları garanti altına almak için de her zaman IP-checksum’ın da yeniden hesaplanması faydalı olacaktır.

Burada wireshark ile ilgili kısa bir not geçelim. Wireshark->Edit->Preferences->Protocols->TCP penceresinde “Validate checksum” işaretli değilse bu kontrol yapılmaz ve ilk bakışta bu tür hatalar görünmeyebilir. Bunu açtığınızda ise çok fazla pakette hata varmış gibi görünecektir. Örneğin client tarafından pcap aldığınızda, outgoing paketlerin hepsinde bu uyarı çıkabilir. Network kartı genelde bu hesaplamaları yapacak şekilde optimize edildiği için, wireshark’ın yakaladığı noktada henüz CRC hesaplanmamış olabilir. Dolayısıyla yanlış olduğunu zannediyor.

Doğru hesaplanmış bir MITM senaryosunun en basit hali şu şekilde olabilir:

Modifiye edilip gönderilecek her paket için şu formül uygulanır:

ackn = seqn-1 + lenn-1
seqn = ackn-1

Burada yine unutulmaması gereken iki nokta var. Birincisi, ack hesaplanırken seq ve len değerleri toplanmadan önce host-byte-order’a çevrilip, pakete yazılmadan önce yeniden network-byte-order’a çevrilmeli. İkincisi, burada gösterilen değerlerin tamamı offset olarak hesaplanıyor. Yani ilk CH paketi geldiğinde client’a ait initial sequence number(ISN) paketteki seq değerinden, server’a ait ISN de aynı paketteki ack değerinden alınıp sonraki hesaplamalar bu sayılara eklenerek yapılmalı.

Devam etmeden önce kısa bir not. SSL session’lardan sonra gönderilen FIN/FINACK/ACK mesajlarının da yukarıdaki şekilde işlenip gönderilmesi çok önemli. Aksi takdirde taraflar handshake’in doğruluğundan emin olamayacak ve devam etmeyeceklerdir.

Ortadaki çizgiye, yani kendi uygulamamıza biraz daha yakından bakalım. Senkronizasyonla ilgili önemli bir sorun var aslında şu an.

İlk CH paketlerinden sonra, bizim ürettiğimiz paketler hep gerçek paketlerin gelmesine bağlı. Örneğin server’dan SH geldiği anda, aslında client’dan gelen CH’ye cevap olarak gönderilecek SH çoktan FS içinde üretilmişti. server’dan gelen paket FS’yi tetikleyerek bu paketin client’a gönderilmesini sağlıyor. Ardından client’dan CKE gelerek FS’nin handshake’e devam etmesini sağlıyor. Burada en önemli varsayım client’dan gelen her pakete karşılık server’dan da bir paket geleceği. Bu doğru olsa, gelen paketleri direk FC/FS’ye besleyip, karşı tarafa ait Fake yapıda bekleyen çıktıları göndermek sorun olmayacak. Ama bu varsayım her zaman tutmuyor. (client’a SH ile gönderilen sertfikada aslında server’ın bilgilerinin bulunmaması sorununa daha sonra geleceğiz)

Aslında daha derinde, bu sorunun en kolay sebebi disassemble edilmiş TCP paketleri olabilir. Normalde receive() ile işletim sisteminden aldığımız paketler, kernel’deki TCP implementasyonu tarafından bize reassemble edildikten sonra verildiği için bu sorunla karşılaşmıyoruz. Ama MITM sırasında raw soketler kullanmak zorunda olmamızdan dolayı, bu tür TCP işlemlerini yapabilecek ayrı bir kod hazırda yoksa, özellikle büyük CRT paketleri birkaç TCP paketine bölünmüş olarak gelip yukarıdaki senkronizasyonu kolayca bozabilir.

Reassemble gerektirmese bile, bazı SSL Server’lar SH içindeki CRT ve SHDone mesajlarını ayrı paketlerde yollayabiliyorlar. İlk SH den sonra bizim client’a ilettiğimiz toplu SH’ye karşılık client da kendi CKE’sini bize dönecek. Burada işler karışabilir. Eğer server’dan SH ile ilgili tüm paketler gelmeden client’ın CKE’si bize gelirse, bu mesajı FC’ye iletemeyeceğiz, çünkü henüz bu mesajı işleyebilecek state’e gelmedi. Öncelikle server’dan sertifikayı alıp kendi pre-master-key’ini üretebilecek duruma gelmesi gerekiyor.