Geçenlerde sunucuda bazı ağlara erişim için routing konfigürasyonu yaptık ve hedef adresi nasıl gittiğini kontrol etmek için meşhur traceroute komutunu kullandık. Fakat bu sefer diğerlerinden farklı olarak, daha önce bu komutu kullanıp sonuçları değerlendirmiş olsam da, gerçekten nasıl çalıştığını öğrenmek istedim.

Bazı şeylerin nasıl çalıştığını sihir ya da bizim tabirimizle şeytan işi olarak tanımlamak kolayımıza gelebiliyor.

Any sufficiently advanced technology is indistinguishable from magic.

Tabi diğer notlarımdan da belki gözlemlediğiniz üzere, biz genelde There is no magic yaklaşımı ile sihrin arkasında yatan mantığı ortaya çıkarıp anlamaya çalışıyoruz. Bu yazıda genel yaklaşımdan farklı olarak, traceroute nasıl çalıştığını klasik anlamda anlatmaktansa kendim nasıl çalıştığını anlamak için neler yaptım ne sonuçlara vardım ondan bahsedeceğim.

İlk olarak tabi kullandığım MacOS bilgisayar üzerinde terminal açıp komutu bilinen bir adresle çalıştırmak oldu.

 > traceroute www.google.com
traceroute to www.google.com (216.239.38.120), 64 hops max, 52 byte packets
 1  172.16.33.1 (172.16.33.1)  18.249 ms  1.462 ms  1.840 ms
 2  88.243.80.1.dynamic.ttnet.com.tr (88.243.80.1)  10.927 ms  9.714 ms  9.555 ms
 3  81.212.73.201.static.turktelekom.com.tr (81.212.73.201)  11.230 ms  28.809 ms  8.312 ms
 4  * * 34-acibadem-sr14s-t2-2---34-kartal-t3-3.statik.turktelekom.com.tr (81.212.217.1)  4240.849 ms
 5  * * *
 6  307-sof-col-2---34-ebgp-acibadem-sr12e-k.statik.turktelekom.com.tr (212.156.104.158)  17.957 ms  17.988 ms  18.058 ms
 7  142.250.168.28 (142.250.168.28)  17.592 ms  16.475 ms
    142.250.167.192 (142.250.167.192)  19.013 ms
 8  * * *
 9  any-in-2678.1e100.net (216.239.38.120)  16.574 ms  15.592 ms  16.383 ms

Yukarıda www.google.com adresine traceroute çektiğimizde, gelen her satırı ulaşmak istediğimiz adrese giderken genellikle paketlerimizin yönlendirildiği birer hop yani durak ya da birer router gibi düşünebilirsiniz.
Diğer kafa karıştıran oluşturan konu ise birden fazla ms yani süre değerleri olabilir. Bunlar bir hop için 3 adet sordu atıp üçünün de dönüş sürelerini gösteriyor.

Bunu yaparken, çok kullandığımız ping aracının da kullandığı ICMP paketleri gönderiliyor. Traceroute her hop için gönderilen paketin TTL değeri 1 rakamından başlayarak arttırılıyor. Bu şekilde gönderilen ve yolculuğuna başlayan paketin her durakta TTL değeri 1 azaltılıyor, ve bu değer 0 olduğunda ise, ilgili router bize TTL Exceeded cevabı geri dönüyor. Bu cevabı alan, bizim kullandığımız araç olan traceroute ise istemci olarak TTL değerini 1 arttırıp, bir sonra hangi router üzerinden bu paket geçecek, evet ise ne kadar sürecek, değil ise hangi router üzerinde takılacak bunu anlamaya çalışıyor. Bunu diyagrama dökersek aşağıdaki gibi gözükecektir.

sequenceDiagram
    participant Local as Local
    participant H1 as 172.16..
    participant H2 as 88.243..
    participant H3 as 81.212..
    participant H4 as 81.212..
    participant H5 as Unreachable
    participant H6 as 212.156..
    participant H7 as 142.250..
    participant H8 as Unreachable
    participant Dest as Google
    
    Local->>H1: TTL=1
    H1-->>Local: 18.249 ms, 1.462 ms, 1.840 ms
    
    Local->>H2: TTL=2
    H2-->>Local: 10.927 ms, 9.714 ms, 9.555 ms
    
    Local->>H3: TTL=3
    H3-->>Local: 11.230 ms, 28.809 ms, 8.312 ms
    
    Local->>H4: TTL=4
    H4-->>Local: *, *, 4240.849 ms
    
    Local->>H5: TTL=5
    H5-->>Local: No Response
    
    Local->>H6: TTL=6
    H6-->>Local: 17.957 ms, 17.988 ms, 18.058 ms
    
    Local->>H7: TTL=7
    H7-->>Local: 17.592 ms, 16.475 ms, 19.013 ms
    
    Local->>H8: TTL=8
    H8-->>Local: No Response
    
    Local->>Dest: TTL=9
    Dest-->>Local: 16.574 ms, 15.592 ms, 16.383 ms

Buraya kadar normal, işin teorik kısmını güzel özetledik fakat bunu yaparken , Wireshark ile gelen giden paketleri incelediğimde şöyle bir sonuç ortaya çıktı.

Capture 1

Benim beklentim, her satır için TTL değerlerinin 1,2,3 diye arttığını görmekti ama ekran görüntüsünde gözüktüğü gibi hepsinde Time to Live kolonunda ,1 yani 1 olarak gözüküyor.

Traceroute Kodunu İnceleyelim

Yukarıdaki kafama takılan soruyu biraz daha araştırarak, sorarak, okuyarak bulacağımdan eminim fakat pratik ile yaptığımda çok daha iyi kavradığımı bildiğim için farklı bir yönden bulmaya karar verdim. Traceroute dediğimiz şey sonunda bir kod parçasının derlenmiş hali olan bir araç, aynı routing mantığını koşan network cihazları, paketimizi gönderen işletim sistemi gibi. En iyi doküman da kod olduğuna göre neden kodunu indirip, inceleyip, debug edip yani pratik yaparak anlamıyoruz?

Kullandığım bilgisayar bir MacOS işletim sistemi üzerinde çalıştığından, hemen Apple firmasına başvuruyoruz. MacOS aslında bir Unix türevi işletim sistemi ,Linux ile aynı araçlara genelde sahip olsa da aynı kaynak kodlara sahip olmak zorunda değil. Genelde MacOS GNU lisans kısıtlamaları sebebiyle BSD türevi işletim sistemlerinin araçlarını kullanıyor. Yani klasik bir Linux işletim sisteminde çalıştırdığınız aynı traceroute GNU kodu iken, MacOS üzerinde bulunan traceroute bir BSD türevi kaynak koda sahip.

Apple bu tarz kullandığı open source araçlar, teknolojiler için bu adreste kaynak kodlarının linklerini yayınlamış. Benim aradığım araç bir network komutu olduğu için network_cmds altında bulunuyor ve bu adresten kaynak kod paylaşılmış.

Derleme Sorunları

Kodu indirdikten sonra önce derlemek ile biraz uğraştım diyebilirim, en büyük sebebi de bir türlü kullanılan bir SO_RECV_ANYIF değerin hem kod içinde hem de sistem kütüphaneleri içinde bulunmamasıydı. Derleme sırasında aşağıdaki hatayı bir türlü geçemedim.

> cc -g -I../network_cmds_lib ../network_cmds_lib/network_cmds_lib.c \
traceroute.c as.c ifaddrlist.c version.c findsaddr-socket.c  -o traceroute -lpcap

traceroute.c:826:35: error: use of undeclared identifier 'SO_RECV_ANYIF'
        (void) setsockopt(s, SOL_SOCKET, SO_RECV_ANYIF, (char *)&on,
                                         ^
1 error generated.

Tahminim Xcode SDK güncellemesi yaparsam ya da ek bir SDK kurarsam düzeleceği yönündeydi fakat bununla uğraşmak istemeyip aradığı tanımı traceroute.h içinde tanımlayıp aşağıdaki gibi geçtim sonra da başarılı şekilde derlendi. Bu arada bu değerin nereden geldiğini bilen varsa öğrenmek isterim.

#ifndef SO_RECV_ANYIF
#define SO_RECV_ANYIF 0x1104
#endif

Başarılı derlendi derlenmesine ama diğer uğraştıran konulardan biri ise bu şekilde derleyip çalıştırdıktan sonra aşağıdaki hataya takıldım bir süre.

> cc -g -I../network_cmds_lib ../network_cmds_lib/network_cmds_lib.c \
traceroute.c as.c ifaddrlist.c version.c findsaddr-socket.c  -o traceroute -lpcap
> sudo ./traceroute www.google.com
traceroute: ifaddrlist: SIOCGIFADDR: awdl0: Can't assign requested address

Biraz kodu inceledikten sonra içinde bol bol koşullu derleme yönergeleri olduğunu fark ettim ve sonrasında HAVE_SYS_SOCKIO_H ve HAVE_SOCKADDR_SA_LEN derleme parametrelerini göndermediğim zaman başarılı şekilde derlenip çalıştı.

cc -g -DHAVE_SYS_SOCKIO_H -DHAVE_SOCKADDR_SA_LEN \
-I../network_cmds_lib ../network_cmds_lib/network_cmds_lib.c \
traceroute.c as.c ifaddrlist.c version.c findsaddr-socket.c  -o traceroute -lpcap

Kod beklediğimden daha kompleks çıktı diyebilirim, sanırım diğer Unix türevi işletim sistemlerinde de çalışması için bol bol koşullu derlemede kullanılan macro parametreleri kullanılmış. Yukarıdaki beni uğraştıran iki parametre de sanırım MacOS için derlendiğinde kullanılması gerekiyordu, indirdiğim kaynak kod içinde CMake, Make gibi bir araç ya da build dokümanı olmadığından bunu biraz deneme yanılma biraz da kodu inceleyerek buldum diyebilirim.

Debugging

Artık kodu debug sembolleri ile derleyebildik ve sıra geldi adım adım kodu çalıştırıp neler yaptığını anlamaya ve ilk aklımıza takılan neden TTL değerinin artmadığını öğrenmeye. Bu aşamada ben LLDB kullanarak kodu debug ettim, ana çalışma akışını öğrendim diyebilirim tabi burada bunu yazmak zor olacağı için, Linux sistemlerde kullandığım ftrace benzeri bir araçla çalışan fonksiyonları çıkarmak istedim, hem ana resim çıkmış olur hem de buraya daha kolay aktarılır.

Dtrace ile her fonksiyon çağrısını izleyebileceğiniz bir script hazırlayıp ardından onun aracılığı ile programı çağırırsanız size tüm fonksiyon çağrılarını gösteriyor.

pid$target:traceroute::entry
{
    printf("%s[%d] %s:%s\n", execname, pid, probemod, probefunc);
}

Yukarıdaki kodu user_functions.d dosyası olarak kaydedip dtrace ile programı çalıştırdığımda aşağıdaki gibi bir çıktı üretiyor.

> sudo dtrace -s user_functions.d -c './traceroute www.google.com'
 1  172.16.33.1 (172.16.33.1)  8.701 ms  1.894 ms  2.130 ms
 2  88.243.80.1.dynamic.ttnet.com.tr (88.243.80.1)  8.757 ms  9.586 ms  9.861 ms
 3  81.212.73.201.static.turktelekom.com.tr (81.212.73.201)  8.834 ms  12.060 ms  11.553 ms
cpu     id                    function:name
  1  65465                       main:entry traceroute[35587] traceroute:main
  2  65470                gethostinfo:entry traceroute[35587] traceroute:gethostinfo
  1  65491        clean_non_printable:entry traceroute[35587] traceroute:clean_non_printable
  1  65471                     setsin:entry traceroute[35587] traceroute:setsin
  1  65472               freehostinfo:entry traceroute[35587] traceroute:freehostinfo
  1  65489                 ifaddrlist:entry traceroute[35587] traceroute:ifaddrlist
  1  65490                  findsaddr:entry traceroute[35587] traceroute:findsaddr
  3  65455                   udp_prep:entry traceroute[35587] traceroute:udp_prep
  3  65482               _osswapint16:entry traceroute[35587] traceroute:_osswapint16
  3  65482               _osswapint16:entry traceroute[35587] traceroute:_osswapint16
  3  65482               _osswapint16:entry traceroute[35587] traceroute:_osswapint16
  3  65484                    p_cksum:entry traceroute[35587] traceroute:p_cksum
  3  65482               _osswapint16:entry traceroute[35587] traceroute:_osswapint16
  3  65493                   in_cksum:entry traceroute[35587] traceroute:in_cksum
  3  65493                   in_cksum:entry traceroute[35587] traceroute:in_cksum
  3  65493                   in_cksum:entry traceroute[35587] traceroute:in_cksum
  3  65474                 send_probe:entry traceroute[35587] traceroute:send_probe
  3  65482               _osswapint16:entry traceroute[35587] traceroute:_osswapint16
  3  65475             wait_for_reply:entry traceroute[35587] traceroute:wait_for_reply
  ...
  ...

Dikkat Çeken Detay

Yukarıdaki debug çıktısına baktığınızda belki dikkatinizi çekmemiş olabilir ama ben adım adım debug yaparken o fonksiyona girdiğinde şaşırmıştım. Fonksiyon çağrılarından birisinin adı udp_prep olduğunu görüyorsunuz sonrasında da, send_probe ve ardından wait_for_reply tekrar edip duruyor.

Koda girip ilgili udp_prep fonksiyonunu aratırsanız ilk karşılaşacağınız yerlerden bir tanesi aşağıda, aslında en büyük ipucu orada açıklanmış.

/* List of supported protocols. The first one is the default. The last
   one is the handler for generic protocols not explicitly listed. */
struct	outproto protos[] = {
	{
		.name = "udp",
		.key = "spt dpt len sum",
		.num = IPPROTO_UDP,
		.hdrlen = sizeof(struct udphdr),
		.port = 32768 + 666,
		.prepare = udp_prep,
		.check = udp_check
	},
  ...
  struct	outproto *proto = &protos[0];

Yukarıdaki açıklamayı okuyunca aslında bendeki en büyük kafa karışıklığı aydınlanmış oldu. Devam edip kodun içinde diğer fonksiyonların içinde nasıl kullanıldığına bakarsanız bunu görebiliyorsunuz. Traceroute aslında sadece ICMP protokolü ile değil, UDP, TCP gibi protokollerle de çalışıyor hatta varsayılan protokol UDP olarak ayarlanmış ve kullandığı port numarası da 32768 + 666 yani 33434.

Trafiği Tekrar Gözden Geçirelim

Daha önce yakaladığımız ve TTL sayısının artmadığı trafiği tekrar yukarıdaki bilgi ile kontrol ettiğimizde, aşağıdaki gibi aslında TTL değerlerinin arttığını ve ilk başta beklediğimiz akışa çok benzediğini görebiliyoruz.

Capture 2

Time to Live sütununda artan TTL değerlerini şimdi görebiliyoruz.

Kafa Karışıklığının Nedeni Neydi?

Benim asıl kafa karışıklığımın sebebi traceroute nasıl çalışıyor diye araştırdığımda aslında sadece ICMP ile çalıştığı yazıyordu. Aslında durum bu değil, hatta örnek olarak Fortigate sitesinden alıntıyı görebilirsiniz.

A traceroute works by sending Internet Control Message Protocol (ICMP) packets, and every router involved in transferring the data gets these packets. The ICMP packets provide information about whether the routers used in the transmission are able to effectively transfer the data.

Genelde nasıl çalıştığı ile ilgili yazılar sadece ICMP protokolünden bahsediyor sanırım bir sebebi de Windows işletim sistemi üzerindeki muadili tracert bu şekilde çalışıyor yani varsayılan protokol olarak ICMP kullanıyor. Fakat MacOS ve Linux işletim sistemlerinde ilk olarak UDP ile paket gönderiyor hatta isterseniz, ICMP ya da TCP üzerinden de paketleri göndermesini sağlayabiliyorsunuz.

Kapanış

Traceroute özellikle sorun network ile ilgili çözme çalışmalarında alet çantamızda bulunması gereken araçlardan bir tanesi, nasıl çalıştığını hangi durumda ne paket gönderdiğini, hangi portu kullandığını kod üzerinden debug ederek, paket dinleyip detaylarını inceleyerek anlamış olduk. Tabi birisi ya bu kadar uğraşa gerek var mıydı, gidip man sayfasını okusaydınız zaten orada yazıyordu da diyebilir ve haklıdır. Teknolojide doküman okumamak adettendir ama man traceroute okusaydık aşağıdaki satırları görebilirdik.

This program attempts to trace the route an IP packet would follow to some internet host by launching UDP probe packets with a small ttl (time to live) then listening for an ICMP “time exceeded” reply from a gateway. We start our probes with a ttl of one and increase by one until we get an ICMP “port unreachable” (which means we got to “host”) or hit a max (which defaults to net.inet.ip.ttl hops & can be changed with the -m flag).

Ama yine de bence oldukça faydalık bir debugging oturumu oldu, sağlıcakla…


<
Previous Post
SSH Trafiğini Çözümleyelim 1 - Patch
>
Blog Archive
Archive of all previous blog posts