C# - Trace funkció használata programból, avagy az ICMP protokoll implementálása

forráskód letöltése
Milyen számítógépeken keresztül érünk el egy web helyet? Ha beírjuk egy böngészőbe, hogy www.SoftwareOnline.hu, akkor vajon mely gépeken keresztül jut el a böngészőnk a megcímzett web helyet kiszolgáló számítógépig? Vajon mennyi idő telik el útközben egy-egy számítógépnél? Melyik a leglassúbb pontja hálózati kapcsolatunknak?
Ezekre a kérdésekre választ kaphatunk a Tracert.exe segédprogram használatával, de mostani cikkünkben arra keresünk megoldást, hogy saját programunk miként képes ezeknek az információknak a lekérdezésére, melyhez programból kell használnunk az ICMP (Internet Control Message Protocol) protokollt.
Az ICMP az IP adatforgalom üzenetküldő protokollja. Működése legtöbbször a háttérben húzódik meg a felhasználó számára észrevétlenül, ugyanakkor léteznek parancssori segédprogramok, melyek csak ICMP-t használva hálózattesztelésre készültek. A protokoll leírása megtalálható az RFC 792-es számú dokumentációjában. Még a TCP/IP korai fázisában fejlesztették ki, ezért gyakorlatilag minden operációs rendszerben megtalálható. Sőt a hozzá tartozó parancssori segédprogramok is hallgatólagos szabvánnyá váltak és használatukban kis eltérést tapasztalunk például a Windows és a Linux verziók között.
Az IP küldést megvalósító aktív hálózati elemek állapotjelzése zajlik az ICMP üzenetekkel. Aktív hálózati elem egy hálózati kártya, egy útválasztó és minden, ami IP csomagokat küld és fogad és rendelkezik IP címmel. Mikor keletkeznek az üzenetek?
  • Ha egy IP csomag nem ért célba.
  • Ha az útválasztó konfigurációja olyan, hogy nem képes az adott feltételekkel továbbítani a csomagot.
  • Ha az útválasztó az adott sebességen nem képes a továbbításra.
  • Illetve, ha az útválasztó átirányítja a csomagot egy másik útvonalra.
  • Hálózati diagnosztikánál.
  • Útválasztó táblák felépítésénél és karbantartásánál.
  • Útválasztó keresésnél.
  • PMTU meghatározásnál (lásd lejjebb).
Az IP adatcsomagok két fő részre oszthatók: fejléc és tartalom. Utóbbi beágyazva tartalmazza az ICMP üzeneteket, tehát IP nélkül nincs ICMP sem.
PMTU meghatározás
Ha két eszköz között létrejön a hálózati kapcsolat, akkor kicserélik egymással a Maximum Segment Size (MSS) (=legnagyobb szegmens méret) értékeiket. Különbözőség esetén mindig a kisebbikkel kommunikálnak. Ha a csomag küldője Windows 2000-et futtató gép, akkor egy flag-ben csomag töredezettségre vonatkozó adatot is küld. Amennyiben a fogadó egy útválasztó, akkor a következő válaszokat adhatja:
  • Elutasítja a csomagot és egy ICMP üzenetben visszajelez a küldőnek. Az ilyen jellegű üzeneteknek ez a célja. Így a felhasználó szempontjából értékes adatátvitel és diagnosztikai szoftverek nélkül megállapítható a kapcsolat létrejötte.
  • Elutasítja a csomagot és visszajelez egy ICMP üzenetben. Ezenkívül elküldi a használható szegmens méretét.
  • Elutasítja a csomagot és nem jelez vissza, mintegy fekete lyukként működik, ami hasonlít egy tűzfal működéséhez.
ICMP üzeneteket használó parancssori segédprogramok
Legalapvetőbb - több operációs rendszeren is meglévő - segédprogram a PING. Ezzel egy korábbi, alább hivatkozott cikkünk már foglalkozott, ahol is a PING funkciót valósítottuk meg programból. A PING lényege, hogy ICMP üzeneteket küld egy IP címmel rendelkező állomásnak (számítógép, útválasztó, átjáró, stb.) és a visszakapott válaszokat feldolgozza és megjeleníti a képernyőn, mérve a küldött és kapott csomagok számát, illetve az átvitelhez szükséges időt.
Echo üzeneteket küld a 192.168.0.10-es IP címmel ellátott gép számára. Ha a célállomás válaszol, akkor kiírja a csomagstatisztikát, ha nem, akkor ezt is jelzi. IP cím helyett gépnevet is használhatunk. Mit jelent a válasz? A két gép között van TCP/IP hálózati kapcsolat. Ha ugyanez IP cím helyett gépnévvel is működik, akkor a névfeloldási rendszer is helyesen van konfigurálva.
Szintén a TTL alkalmazására épül a hálózati útvonalkövetésre használható TRACERT program. Feladata megjeleníteni a képernyőn, hogy az ICMP csomagok milyen állomásokon keresztül haladnak. Hasonlít a PING-hez, de az ugrásokról is mutat adatokat. Használata nagyon egyszerű: adjuk meg paraméterként a tesztelendő célállomás nevét. A többi megjelenik a monitoron.
tracert www.SoftwareOnline.hu
Ezen alkalmazás által szolgáltatott eredmény fogjuk most programból lekérdezni.
A példaprogram
A hálózati kapcsolathoz egy Socket osztályt hozunk létre, melyben a protokoll típusát ICMP-re választjuk.
A megadott web cím, vagy IP alapján létrehozunk egy IPEndPoint osztályt, mely a cég gépet fogja reprezentálni. Szükségünk lesz egy kiindulási pontra is, amihez szintén egy IPEndPoint osztályt használunk és amely a saját gépünket adja meg.
    private void button1_Click(object sender, System.EventArgs e)
    {
      Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
      IPEndPoint iepDest = new IPEndPoint(Dns.Resolve(textBox1.Text).AddressList[0],80);
      IPEndPoint iepSource = new IPEndPoint(Dns.GetHostByName(Dns.GetHostName()).AddressList[0],80);
      EndPoint epSource = (EndPoint)iepSource;
Létrehozunk és feltöltünk adatokkal egy ICMP struktúrát, mely majd a teszthez kell.
      ICMP ip= new ICMP();
      ip.type = ICMP_ECHOREQ; 
      ip.code = 0;
      ip.checksum = 0;
      ip.id = (ushort)DateTime.Now.Millisecond;
      ip.seq  = 0;
A tesztelés folyamán egy adathalmazt küldünk a célgép felé, hasonlóan ahhoz, mint amikor a PING funkciót valósítjuk meg. Ehhez feltöltünk egy újabb struktúrát adatokkal, ami alapján létrehozunk egy adatcsomagot a CreatePacket függvény hívásával.
      REQUEST req= new REQUEST();
      req.m_icmp= ip;
      req.m_data = new Byte[PACKET_SIZE];      
      for (int i=0; i<req.m_data.Length; i++)
      {
        req.m_data[i] = (byte)'#';
      }
      byte[] baSend = CreatePacket(req);
      int retCode = 0;
Ezt követően kezdjük el a tesztet, mely egy ciklus segítségével zajlik, ami biztosítja, hogy egymás után próbáljuk meg elérni a hálózati úton az egyes gépeket.
      for(int i=1; i<=MAX_TTL; i++)
      {
        ...
Elküldjük az adatcsomagot, majd visszaolvassuk a választ.
        s.SendTo(baSend, baSend.Length, SocketFlags.None, iepDest);
        retCode = s.ReceiveFrom(baReceive, baReceive.Length, SocketFlags.None, ref epSource);
Az eltelt időből meghatározzuk, hogy mennyi idő alatt kaptunk választ a kérésünkre.
        TimeSpan ts = DateTime.Now - dt;
Ezek után összegyűjtjük az adatokat és megjelenítjük egy ListView kontrolba. Az adatok tartalmazzák az adott lépésnek a sorszámát, az aktuálisan elért gép nevét, IP címét, valamint a válaszhoz szükséges időt ezredmásodpercben.
        it[0] = i.ToString();
        it[2] = ((IPEndPoint)epSource).Address.ToString();
        it[1] = Dns.GetHostByAddress(it[2]).HostName;
        it[3] = ts.Milliseconds.ToString() + " ms";
        listView1.Items.Add(new ListViewItem(it)); 
        ...
      }
      string[] m4 = {"", "--- end ---", "", ""};
      listView1.Items.Add(new ListViewItem(m4));
    }