TCP: Pelengkap IP

Internet Protocol (IP) adalah protokol yang tidak reliable. Jika terjadi kesalahan saat mengirim pesan, ia akan mengabaikan packet dan mengembalikan error ICMP saja. Untuk itu, TCP terlahir pada layer transport sebagai protokol yang dapat di-“andal”-kan (reliable). TCP adalah protokol yang rumit, yang terdiri atas sekumpulan software untuk menyediakan layanan komunikasi. Kali ini aku akan mempelajari bagian yang bisa dilihat dengan mata kepala sendiri dari TCP yaitu TCP Protocol Data Unit (PDU). TCP PDU adalah bagian yang dikirim sebagai packet. Berikut ini adalah contohnya:

xx xx 04 11 00 50 68 91   34 ad 00 00 00 00 70 02
40 00 a9 63 00 00 02 04   05 b4 01 01 04 02

Dua byte pertama menunjukkan informasi source port (dalam contoh adalah, 0x0411 atau port 1041) dan dua byte berikutnya menunjukkan informasi destination port (dalam contoh adalah 0x0050 atau port 80). TCP memanfaatkan port untuk membentuk komunikasi yang reliable. Kombinasi antara alamat IP dan nomor port disebut socket. Beberapa port yang berbeda di sebuah host dapat mengakses sebuah destination port yang sama, berkat penggunaan multiplexing. Sebagai contoh, sebuah server web yang membuka port 80 dapat melayani banyak pengunjung tanpa harus membuka port baru yang berbeda untuk setiap pengunjung.

Empat byte berikutnya adalah nilai sequence number. Nilai 0x689134AD disini dihasilkan secara acak. Jika seseorang dalam jaringan dapat menebak dan melanjutkan sequence number, maka ia dapat saja membajak komunikasi TCP kita (oleh sebab itu nilai sequence number diawali dengan angka acak). Empat byte setelah ini adalah acknowledgment number, yang nilainya adalah nilai sequence number untuk packet berikutnya (pada SYN, ini adalah nilai sequence number ditambah dengan 1).

Nilai acknowledgment number pada contoh adalah 0 karena ini adalah sebuah TCP open (awal dari komunikasi/SYN). Berikut ini adalah contoh jawaban dari host tujuan:

xx xx 00 50 04 11 93 cf  35 e5 68 91 34 ae 70 12
83 2c 9c 71 00 00 02 04  05 b4 01 01 01 04 02

Kali ini source port adalah port 80 dan destination adalah port 1041. Disini, host tujuan memiliki nilai sequence number (atau initial sequence number) tersendiri, yaitu 0x93cf35e5. Sementara nilai acknowledgment numbernya adalah 0x689134ae. Ini menunjukkan ia merespon packet yang aku kirim sebelumnya yang memiliki sequence number 0x689134ad.

Kembali ke TCP yang aku kirim pertama kali, empat byte setelah acknowledgment number memiliki nilai 0x7002, atau dalam binary berupa 0111 0000 0000 0010. Empat bit pertama menunjukkan ukuran header dalam satuan 32-bit (4 byte). Nilai disini adalah 0111 (atau 7 desimal) sehingga ukuran header adalah 7 x 4 byte = 28 byte. Empat bit berikutnya harus bernilai 0 (reserved). Setelah itu, setiap bit menunjukkan informasi flag berikut secara berurutan: CWR, ECE, URG, ACK, PSH, RST, SYN, FIN. Pada contoh, bit SYN bernilai 1, menunjukkan bahwa ini adalah awal dari koneksi. Sementara itu, pada contoh packet yang dikirim oleh server web, flag ACK dan SYN bernilai 1.

Dua byte setelah itu adalah ukuran window (range sequence number yang valid untuk diterima). Setelah itu ada dua byte checksum, dan dua byte urgent pointer. Nilai urgent pointer hanya dipakai jika flag URG bernilai 1. Setelah itu ada 8 byte options atau lebih (selalu kelipatan 8) atau tidak ada sama sekali. Setiap options diawali dengan sebuah byte yang disebut option number (nilai 1 untuk NOP, nilai 2 untuk maximum segment size, dsb), kemudian sebuah byte yang menunjukkan jumlah byte untuk option tersebut, lalu diikuti dengan nilai option jika ada.

Untuk membuat sebuah koneksi dari client ke server, TCP menggunakan three-way handshake. Client akan mengirim packet dengan flag SYN bernilai 1 ke server dan nilai sequence number yang acak, misalnya 0x689134ad. Lalu, server akan merespon dengan mengirim packet dengan flag SYN bernilai 1 dan ACK bernilai 1. Selain itu, packet ini juga harus memiliki acknowledgment number yang sesuai, misalnya pada contoh, 0x689134ae. Packet dari server sendiri memiliki sequence number yang acak, misalnya 0x93cf35e5. Berikutnya, client akan merespon dengan mengirim packet dengan flag ACK bernilai 1 ke server. Berdasarkan pada contoh, packet ini memiliki sequence number 0x689134ae dan acknowledgment number 0x93cf35e6. Koneksi baru terbentuk setelah proses handshake ini. Jika digambarkan, mungkin proses handshake akan terlihat seperti ini:

CLIENT                                   SERVER
  | SYN (SEQ=0x689134ad, ACK=0)  ----->   |
  |                                       |
  |                                       |
  | <------   SYN, ACK (SEQ=0x93cf35e5,   |       
  |                     ACK=0x689134ae)   |   
  |                                       |   
  |                                       |   
  | ACK (SEQ=0x689134ae,  ------------>   |
  |      ACK=0x93cf35e6)                  |

Setelah ini, proses transfer data dapat dilakukan. Misalnya, seperti pada proses berikut (nilai sequence number disini tidak ada hubungannya dengan yang sebelumnya):

CLIENT                                   SERVER
  |                                       |
  | ACK, PUSH (SEQ=0x38affe14,  ------->  |
  |            ACK=0x114c618c)            |
  |                                       |
  |                                       |
  | <--------- ACK (SEQ=0x114c618c,       |
  |                 ACK=0x38affff3)       |
  |                                       |
  |     DATA                              |
  | <--------- ACK (SEQ=0x114c618c,       |   
  |                 ACK=0x38affff3)       |   
  |                                       |   
  | ACK (SEQ=0x38affff3, ---------------> |
  |      ACK=0x114c66f0)                  |
  |                                       |
  |                                       |
  |     DATA                              |
  | <---------- ACK (SEQ=0x114c66f0,      |   
  |                  ACK=0x38affff3)      |   
  |                                       |   
  | ACK (SEQ=0x38affff3, ---------------> |
  |      ACK=0x114c6c54)                  |
  |                                       |
  |     DATA                              |
  | <---------- ACK (SEQ=0x114c6c54,      |
  |                  ACK=0x38affff3)      |

Koneksi TCP secara normal akan berakhir saat salah satu pihak mengirim packet dengan flag FIN aktif yang meminta penerima agar menutup koneksi. Si penerima akan merespon dengan ACK dan FIN ke pengirim agar sang pengirim juga ikut menutup koneksi.

Iklan

Filter Di WinPcap: Nilai Tambah Sebuah Library

Program PING di tulisan sebelumnya baru sampai mengirim ECHO REQUEST saja. Untuk melengkapinya, aku akan mencoba menambahkan kode untuk membaca ECHO REPLY. Tapi kali ini tidak dengan memakai callback karena ada cara lain yang lebih tepat, yaitu dengan menggunakan fungsi pcap_next_ex() yang men-capture packet sesuai kebutuhan saja. Ini adalah contoh kode program yang aku buat:

int status;
while ((status=pcap_next_ex(handle, &header, &packetData))>=0) {
  if (status==0) break;
  struct ECHO_REPLY *echoReply = (struct ECHO_REPLY*)((void*)packetData);
  printf("Menerima ECHO REPLY dengan sequence %d.\n", echoReply->icmp_sequence);
  printf("Data ECHO:\n");
  for (int i=1; i<=32; i++) {     printf("%02x ", echoReply->icmp_data[i-1]);
    if (i%8==0) printf("\n");
  }
  printf("\n");
}

Fungsi pcap_next_ex() akan mengembalikan nilai 1 jika packet dibaca tanpa masalah, nilai 0 jika timeout, nilai -1 jika terjadi error, dan nilai -2 jika EOF saat membaca offline capture. Setelah membaca sebuah packet, aku menampikan nilai icmp_sequence dan juga data yang disisipkan bersama packet ICMP ECHO tersebut.

Sampai disini, akan ada satu masalah! pcap_next_ex() akan mencapture semua paket yang lalu-lalang di network card, baik yang dikirim keluar maupun dikirim masuk, baik TCP, UDP maupun ICMP. Padahal kode program di atas langsung mengandaikan bahwa packet yang diterima adalah packet ICMP ECHO REPLY dari IP tertentu yang dikirim ke komputer ini. Di saat-saat seperti ini, fitur filter milik WinPcap sangat dibutuhkan. Fitur ini dapat menyaring packet apa saja yang akan diterima oleh aplikasi berdasarkan ekspresi boolean yang aku tentukan lewat kode program. Untuk itu, aku harus melibatkan dua fungsi, yaitu pcap_compile() dan pcap_setfilter(). Fungsi pcap_compile() akan menerjemahkan ekspresi dalam bentuk teks ke dalam byte code, kemudian pcap_setfilter() akan menerapkan byte code tersebut. Berikut ini adalah contoh penggunaan pcap_compile():

if (pcap_compile(handle, &filterCode,
  "icmp[icmptype] = 0 and icmp[icmpcode] = 0 and src host 192.168.1.1", 1, 0) < 0) {
  printf("ERROR Compiling Filter\n");
  pcap_close(handle);
  exit(EXIT_FAILURE);
}

filterCode disini adalah sebuah struct bpf_program yang merepresentasikan filter yang sesungguhnya. Parameter terakhir seharusnya berupa network mask (seperti 0xffffff untuk 255.255.255.0). Nilai ini hanya dibutuhkan untuk menyaring packet broadcast, dan karena aku tidak membutuhkan packet broadcast maka aku mengabaikan nilai ini. Lalu ekspresi untuk filter dengan jelas menyatakan untuk hanya menampilkan packet ICMP dengan field icmptype = 0 dan icmpcode = 0 (atau dengan kata lain, ECHO REPLY), dan dengan source address dari 192.168.1.1.

Setelah melakukan proses compile, aku dapat menerapkan filter seperti pada kode berikut:

if (pcap_setfilter(handle, &filterCode) < 0) {
  printf("ERROR Setting Filter\n");
  pcap_close(handle);
  exit(EXIT_FAILURE);
}

Setelah menambahkan fungsi pcap_compile() dan pcap_setfilter() sebelum pcap_next_ex(), akhirnya program PING sederhana ini selesai juga. Saatnya beralih ke topik berikutnya.

Mengirim Raw Packet: Latihan Ping

Setelah sebelumnya aku belajar membaca raw packet yang diterima network card, kali ini aku ingin mencoba mengirim raw packet secara langsung. Keuntungannya adalah aku bisa bebas menentukan semua parameter packet yang aku kirim. Sebagai latihan, aku akan mencoba membuat program yang mirip seperti program PING yang mengirim dan menerima packet ICMP ECHO.

Format packet ICMP ditentukan oleh RFC 792. Berikut ini adalah contoh packet ECHO REQUEST yang dikirim oleh program PING:

xx xx 08 00 4a 5c 02 00    01 00 61 62 63 64 65 66
67 68 69 6a 6b 6c 6d 6e    6f 70 71 72 73 74 75 76
77 61 62 63 64 65 66 67    68 68

Dan ini adalah contoh ECHO REPLY atas packet di atas:

xx xx 00 00 52 5c 02 00    01 00 61 62 63 64 65 66
67 68 69 6a 6b 6c 6d 6e    6f 70 71 72 73 74 75 76
77 61 62 63 64 65 66 67    68 69

Byte awal 08 menunjukkan kalau packet tersebut adalah ECHO REQUEST, dan byte 00 menunjukkan kalau packet tersebut adalah ECHO REPLY. Byte kedua (CODE) berisi nilai 00. Kemudian diteruskan dengan dua byte checksum. Dua byte berikutnya adalah IDENTIFIER, dilanjutkan dengan dua byte SEQUENCE NUMBER. Sepertinya program PING selalu menggunakan IDENTIFIER dengan nilai 0x0200. Nilai SEQUENCE NUMBER dipergunakan untuk mencocokkan antara request dan reply. Setelah itu PING mengirimkan 32 byte data yang berupa deretan huruf.

Sebelum memulai mengirim packet, aku harus memastikan bawah packet ICMP di-enkapsulasi lagi oleh packet IP, lalu oleh packet Ethernet. Untuk mempermudah pembuatan program, aku membuat sebuah struct sederhana yang merepresentasikan packet ECHO REQUEST seperti berikut ini:

struct ECHO_REQUEST {
  u_char mac_destination[6];
  u_char mac_source[6];
  u_short type;
  u_char ip_version_headerlength;
  u_char ip_services;
  u_short ip_totalLength;
  u_short ip_identification;
  u_char ip_flag;
  u_char ip_fragment_offset;
  u_char ip_ttl;
  u_char ip_protocol;
  u_short ip_checksum;
  u_char ip_source[4];
  u_char ip_destination[4];
  u_char  icmp_type;
  u_char  icmp_code;
  u_short icmp_checksum;
  u_short icmp_identifier;
  u_short icmp_sequence;
  u_char  icmp_data[32];
};

Jika aku ingin men-PING komputer dengan IP 192.168.1.1 dari komputer dengan IP 192.168.1.2, maka aku dapat mengisi setiap field di packet seperti:

void initializeEchoRequestPacket(ECHO_REQUEST *echoRequest) {
  u_char mac_destination[6] = {0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
  valuecpy(echoRequest->mac_destination, mac_destination, 6);
  u_char mac_source[6] = {0x2, 0x2, 0x2, 0x2, 0x2, 0x2 };
  valuecpy(echoRequest->mac_source, mac_source, 6);
  echoRequest->type = 0x0008;

  echoRequest->ip_version_headerlength = 0x45;
  echoRequest->ip_services = 0x00;
  echoRequest->ip_totalLength = 0x3C00;
  echoRequest->ip_identification = 0x0400;
  echoRequest->ip_flag = 00;
  echoRequest->ip_fragment_offset = 0x0000;
  echoRequest->ip_ttl = 0x80;
  echoRequest->ip_protocol = 0x01;
  echoRequest->ip_checksum = 0x0000;
  u_char ip_source[4] = {192,168,1,2};
  valuecpy(echoRequest->ip_source, ip_source, 4);
  u_char ip_dest[4] = {192,168,1,1};
  valuecpy(echoRequest->ip_destination, ip_dest, 4);

  echoRequest->icmp_type = 0x08;
  echoRequest->icmp_code = 0x00;
  echoRequest->icmp_checksum = 0x0000;
  echoRequest->icmp_identifier = 0x0002;
  echoRequest->icmp_sequence = 0x000d;
  for (int i=0; i<32; i++) {     echoRequest->icmp_data[i] = 'B';
  }
}

Agar packet ICMP ECHO REQUEST ini dapat dikirim dan diterima, selain mengisi IP address dengan benar, aku juga harus mengisi nilai MAC Source dan MAC Destination (yang merupakan bagian dari Ethernet Frame) dengan benar. Selain itu, aku juga harus melakukan kalkulasi untuk menghasilkan checksum dengan benar. Proses kalkulasi dan contoh kode program untuk kalkulasi checksum dapat dilihat di RFC 1071. Ini adalah contoh fungsi sederhana untuk menghitung nilai checksum untuk IP Header:

void calculateIPChecksum(ECHO_REQUEST *packet) {
  u_short *shortValue = (u_short*)
    (&packet->ip_version_headerlength);
  long totalValue = 0;

  for (int i=0; i<10; i++) {     
    totalValue += (*shortValue);     
    shortValue++;   
  }   
  while (totalValue>>16) {
    totalValue = (totalValue & 0xffff) + (totalValue>>16);
  }
  packet->ip_checksum = ~totalValue;
}

Fungsi di atas mengandaikan IP Header berukuran fixed 20 bytes atau 10 words. Kalkulasi checksum dilakukan dengan menjumlahkan seluruh word (integer 16-bit) yang membentuk IP Header. Jika hasil jumlah dalam bentuk angka 16-bit ini mengandung carry/overflow , maka carry dari MSB dijumlahkan ke LSB, dan ini dilakukan berulang sampai tidak ada carry lagi. Dan hasilnya, 1’s complement dari nilai tersebut adalah nilai cheksum yang dipakai.

Dan sebagai langkah terakhir untuk mengirim ECHO REQUEST, aku harus melakukan proses pengiriman packet. Cara yang paling gampang adalah dengan menggunakan fungsi pcap_sendpacket(). Sebagai contoh, aku mengirimkan 15 kali ECHO REQUEST dengan menggunaakan kode seperti berikut:

for (int j=0; j<15; j++) {   
  struct ECHO_REQUEST *echoRequest = 
    new struct ECHO_REQUEST();   
  initializeEchoRequestPacket(echoRequest);   
  echoRequest->ip_identification = (u_short) j;
  echoRequest->icmp_identifier = 0x0002;
  echoRequest->icmp_sequence = (u_short) j;
  calculateIPChecksum(echoRequest);
  calculateICMPChecksum(echoRequest);
  if (pcap_sendpacket(handle, (u_char*)((void*)echoRequest), 74)) {
    printf("ERROR: %s\n", pcap_geterr(handle));
    exit(EXIT_FAILURE);
  }
  delete echoRequest;
}

O ya, sebagai data pada ECHO REQUEST, aku mengirimkan 32 byte huruf ‘B’. Sebenarnya, aku punya kebebasan untuk memberikan nilai apa saja disini. Salah satu ide kreatif yang pernah aku temui adalah mengirimkan data melalui ICMP ECHO, seperti isi web page atau text chatting. Administrator cenderung memblokir komunikasi TCP/IP berdasarkan port. Jika ia berbaik hati membolehkan packet ICMP REQUEST keluar masuk dengan leluasa [test dengan mencoba apakah program PING dapat dipakai], maka seseorang bisa saja melakukan tunneling. Misalnya, request HTTP disisipkan sebagai data dalam packet ICMP ECHO REQUEST, kemudian diterima oleh komputer lain yang bebas dari blokiran. Program spesial di komputer lain tersebut membaca data spesial di ICMP ECHO REQUEST tersebut, lalu melakukan koneksi internet, dan mengirimkan hasilnya ke komputer pengguna, juga dalam bentuk ICMP ECHO REQUEST/REPLY. Sang administrator di ruangan khususnya, kini sedang serius membaca log firewall (atau mungkin membaca chat gadis-gadis QA sambil sesekali tertawa, no offense here, just a speculation), hanya akan melihat dua komputer saling nge-ping.

Network Packet Programming: Membaca Packet Dengan Callback

Setelah berhasil mendapatkan network device yang dapat di-sniffer, aku akan mencoba membaca packet milik network device tersebut. Aku harus mulai dengan fungsi pcap_open_live(). Setelah itu, untuk membaca packet, WinPcap menawarkan dua cara, dengan callback atao tanpa callback (cara biasa). Pada latihan kali ini, aku akan mencoba memakai callback, sehingga fungsi yang perlu aku panggil adalah pcap_dispatch() atau pcap_loop(). Aku akan memakai pcap_loop() yang akan terus menunggu hingga jumlah packet yang aku tentukan tercapai, walaupun hal ini dapat menyebabkan blocking jika jaringan sedang sibuk.

Untuk mempersingkat tulisan, aku tidak akan menyertakan kode program untuk menampilkan informasi device lagi. Aku hanya menambahkan sedikit kode yang memberi pilihan bagi user untuk menentukan device mana yang akan di-capture:

int pilihan=1, i=1;
printf("Masukkan nomor perangkat yang akan di-capture: ");
scanf_s("%d", &pilihan);
currentDevice = devices;
while (pilihan!=i) {
  currentDevice = currentDevice->next;
  i++;
}

Setelah itu, aku memanggil pcap_open_live() dengan kode seperti:

pcap_t *handle = pcap_open_live(currentDevice->name, 65535, 1, 1000, pesanError);
if (handle==NULL) {
  printf("ERROR: %s\n", pesanError);
  pcap_freealldevs(devices);
  exit(EXIT_FAILURE);
}

Pada pcap_open_live() di atas, aku men-capture maksimal 65.535 byte (seharusnya ini adalah maksimal ukuran kebanyakan packet); aku juga mengaktifkan modus promiscuous, yaitu tidak hanya menangkap packet yang ditujukan untuk komputer-ku tapi juga packet yang dalam perjalanan ke komputer lain tapi harus singgah sebentar ke komputer-ku (well, orang yang iseng pasti sangat suka ini!); dan aku menentukan read timeout sebesar 1000 ms.

Sampai pada bagian ini, aku tidak membutuhkan lagi informasi devices, sehingga aku bisa membebaskan alokasi memori untuk linked list yang berisi informasi tersebut. Setelah itu, aku bisa mendaftarkan fungsi callback:

pcap_freealldevs(devices);
pcap_loop(handle, 0, handlerPacketMasuk, NULL);

Sekarang, setiap kali ada packet yang masuk, fungsi handlerPacketMasuk() akan dikerjakan. Berikut ini adalah contoh fungsi tersebut:

void handlerPacketMasuk(u_char *user,
       const struct pcap_pkthdr *pkt_header,
       const u_char *pkt_data) {

  for (u_int i=1; i<=(pkt_header->len); i++) {
    printf("%02x ", pkt_data[i-1]);
    if (i%8==0) printf("\n");
  }

  printf("\n\n");

}

Fungsi di atas akan menampilkan isi packet dalam tampilan 8 byte per baris. Setidaknya hari ini aku sudah membuat sebuah program sniffer primitif.

Network Packet Programming: Percobaan Pertama

Sehubungan dengan topik yang sedang dipelajari, alangkah menyenangkannya jika aku dapat membuat program untuk menangkap dan mengirim packet secara langsung pada data link layer. Teman-temanku yang suka iseng pasti pernah menggunakan program serupa untuk menyadap atau memblokir koneksi internet seseorang. Tool tersebut harus dapat membuat packet palsu, dan ini hanya bisa dilakukan di data link layer. Sayangnya, Windows tidak menyediakan cara gampang untuk memodifikasi packet selain dengan membuat NDIS intermediate driver. Tapi kabar baiknya, ada sebuah library open source (plus driver siap jadi, tentunya) yang dapat digunakan untuk keperluan ini, yaitu WinPCap. Wireshark adalah salah satu tool yang memanfaatkan WinPCap untuk men-capture packet.

Setelah men-download WinPcap Developer Pack, aku akan mendapatkan beberapa header dan static library yang siap dipakai oleh program. Sekarang, aku akan menyiapkan project baru di Visual C++. Aku masih menggunakan Visual Studio 2008 dan sudah hampir setahun lebih aku tidak menyentuh gadis ini (kabarnya Visual Studio 2010 beta sudah dapat dicoba). Setelah sedikit meluangkan waktu untuk menyesuaikan diri, aku meng-import header-header yang ada. Aku juga tidak lupa menambahkan referensi bagi compiler, dengan men-klik kanan nama project dan memilih properties, C/C++, General. Lalu pada Additional Include Directories, aku menambahkan folder yang berisi header tersebut. Hal yang sama juga aku lakukan pada static library, dengan memilih Linker, Input, dan mengisi Additional Dependencies dengan lokasi static library milik WinPCap (wpcap.lib dan Packet.lib).

Aku mulai dengan membuat kode seperti:

#include "pcap.h"
#include <conio.h>

void exitHandler() {
  printf("Tekan sembarang tombol untuk keluar dari program...");
  _getch();
}

int main() {
  atexit(exitHandler);

  ...

  exit(EXIT_SUCCESS);
}

Langkah pertama adalah aku akan menampilkan perangkat network apa saja yang tersedia dan dapat dipergunakan oleh WinPcap, dengan memanggil fungsi pcap_findalldevs(). Bila ingin men-capture packet milik network card yang berada di jaringan remote (bukan di komputer lokal), aku harus menggunakan fungsi pcap_findalldevs_ex(). Syarat lainnya adalah komputer remote tersebut harus menjalankan daemon rpcapd.exe (terdapat di folder instalasi WinPcap).

pcap_if_t *devices;
char pesanError[PCAP_ERRBUF_SIZE];
if (pcap_findalldevs(&devices, pesanError)==-1) {
  printf("ERROR: %s\n", pesanError);
  exit(EXIT_FAILURE);
}

pcap_findalldevs() akan menghasilkan nilai -1 jika terjadi kesalahan saat mencari device apa saja yang dapat dipergunakan. Jika ia sukses, maka daftar device yang ada akan disimpan dalam struktur linked list pcap_if_t. Berikut ini adalah contoh kode program untuk menampilkan isi pcap_if_t:

#include "pcap.h"
#include <conio.h>

char *iptos(u_long in);

void exitHandler() {
  printf("Tekan sembarang tombol untuk keluar dari program...");
  _getch();
}

int main() {
  atexit(exitHandler);
  pcap_if_t *devices;
  char pesanError[PCAP_ERRBUF_SIZE];
  if (pcap_findalldevs(&devices, pesanError)==-1) {
    printf("ERROR: %s\n", pesanError);
    exit(EXIT_FAILURE);
  }

  pcap_if_t *currentDevice = devices;

  while (currentDevice != NULL) {

    printf("Device: %s\n", currentDevice->name);
    printf("Description: %s\n",
    currentDevice->description ? currentDevice->description : "(informasi tidak
      tersedia)");

    pcap_addr *address = currentDevice->addresses;
    while (address != NULL) {
      if (address->addr->sa_family == AF_INET) {
        sockaddr_in *sockaddr = (struct sockaddr_in *) address->addr;
        printf("Address: %s\n", iptos(sockaddr->sin_addr.s_addr));
      }
      address=address->next;
    }
    currentDevice = currentDevice->next;
    printf("\n\n");
  }

  pcap_freealldevs(devices);
  exit(EXIT_SUCCESS);
}

/* From tcptraceroute, convert a numeric IP address to a string */
#define IPTOSBUFFERS 12
char *iptos(u_long in)
{
  static char output[IPTOSBUFFERS][3*4+3+1];
  static short which;
  u_char *p;
  p = (u_char *)&in;
  which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
  sprintf_s(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
  return output[which];
}

Program di atas juga menampilkan alamat IP untuk setiap device yang disimpan dalam struktur linked list pcap_addr jika ada. Untuk menerjemahkan alamat IP numerik dan menampilkan ke dalam bentuk String (berbahagialah programmer Java yang punya toString()), aku men-copy paste fungsi iptos() yang ada di dokumentasi WinPcap. Dan lagi-lagi tidak seperti di Java, aku harus membebaskan alokasi memory setelah aku tidak membutuhkannya, dalam hal ini sudah ada fungsi pembantu pcap_freealldevs().

ICMP: Sang Pemberitahu Masalah

Komunikasi pesan dari pengirim hingga penerima mungkin saja mengalami masalah ditengah saja. Untuk memberi tahu kepada pengirim bahwa telah terjadi suatu masalah, Internet Control Message Protocol (ICMP) siap bekerja. Berikut ini adalah contoh packet ICMP:

.. .. 03 03 6b 69 00 00  00 00 xx xx xx xx
xx xx xx xx xx xx xx xx  xx xx xx xx xx xx
...

Byte pertama menunjukkan jenis pesan ICMP. Nilai 03 menunjukkan ini adalah pesan “Destination Not Reachable”. Nilai lain yang mungkin adalah:

00 = Echo Reply
03 = Destination Not Reachable
04 = Source Quench
05 = Redirection Required
08 = Echo Request
11 = Time To Live Exceeded
12 = Parameter Problem
13 = Timestamp Request
14 = Timestamp Reply
17 = Address Mask Request
18 = Address Mask Reply

Banyak utility jaringan yang memanfaatkan ICMP, misalnya PING yang akan mengirim “Echo Request” dan memeriksa “Echo Reply”.

Byte kedua berisi kode untuk memperjelas pesan ICMP. Pada contoh di atas, byte kedua bernilai 03, yang menunjukkan informasi “Port unreachable”. Setelah itu ada dua byte checksum.

Byte ke-lima sampai byte ke-delapan seharusnya berisi parameter. Tetapi tidak semua pesan ICMP mengandung parameter, dan kebetulan “Destination Not Reachable” tidak membutuhkan parameter, sehingga nilainya 00 semua.

Byte berikutnya merupakan isi dari pesan, dan layout-nya berbeda-beda tergantung pada jenis pesan ICMP. Pada contoh di atas, byte berikutnya mengandung packet IP asli yang mengalami “Destination Not Reachable”. Packet asli tersebut tidak ditampilkan, hanya berupa tanda “xx” saja.

Internet Protocol: Pelajaran Praktek Pertamaku

Salah satu bidang komputer yang sangat menarik untuk dipelajari adalah networking. Sayangnya, aku tidak pernah punya kesempatan untuk menyentuh topik ini sebelumnya karena berbagai kendala. Untungnya, sekarang aku memiliki peluang untuk belajar. Tentu saja kesempatan ini tidak akan aku sia-siakan begitu saja karena sangat banyak yang belum aku mengerti. Aku akan mempelajari networking mulai dari nol. Internet adalah jaringan yang paling gampang di-akses, jadi topik yang pertama aku pelajari adalah Internet Protocol.

Sebagai bahan pembelajaran, aku menggunakan Wireshark untuk men-capture packet-packet yang dikirim saat aku membrowse sebuah situs, sebagai contoh developers.sun.com. Aku cukup terkejut karena Wireshark juga dapat men-capture frame PPP. Karena aku menggunakan koneksi dial-up melalui modem HP, sebenarnya aku mengakses jaringan Ethernet melalui emulasi PPP. Aku akan mencoba mempelajari frame PPP suatu hari nanti, sekarang aku ingin tahu bagaimana Internet Protocol bekerja.

Sebuah packet Internet Protocol (IP) dapat berukuran hingga maksimum 65.535 bytes. Tapi pada hasil capture-ku, packet IP yang terbesar berukuran di antara 1.280-2.559 bytes. IP memiliki kemampuan untuk membagi informasi ke dalam beberapa datagram yang lebih kecil sesuai kemampuan jaringan (disebut fragmentation), kemudian digabungkan kembali di komputer penerima (disebut reassembly).

Berikut ini adalah contoh datagram IP (sebenarnya hanya header saja):

xx xx xx xx xx xx xx xx    xx xx xx xx xx xx 45 00
00 30 00 a8 40 00 80 06    02 04 0a 0a ?? ?? 48 05
7c 41 yy yy yy yy yy yy    yy yy yy yy yy yy yy yy
...

Bagian yang diberi tanda “xx” adalah header Ethernet yang secara otomatis ditambahkan melalui proses yang disebut encapsulation. Bagian yang diberi tanda “yy” merupakan ‘isi’ dari packet IP tersebut.

Byte pertama berisi nilai 45 (heksadesimal) memiliki dua arti. Sebelumnya, aku menerjemahkan 45H ke dalam binary 0100 0101. Empat bit pertama dari binary tersebut, yaitu 0100 (atau dalam desimal, angka 4) menunjukkan versi IP yang aku pakai. Terlihat bahwa aku menggunakan IP versi 4. Versi lain yang mulai banyak didukung adalah IP versi 6 (IPv6). Lalu empat bit berikutnya, 0101 (atau dalam desimal, angka 5) menunjukkan ukuran header. Satuan yang dipakai adalah 32-bit (word). Jadi angka 5 menunjukkan header berukuran 5 words, atau 5 x 32-bit = 160 bit atau 20 bytes. Ini adalah ukuran paling minimum untuk sebuah header IP.

Byte berikutnya menunjukkan informasi Service Type, seperti datagram precedence, delay, throughput, dan reliability. Pada umumnya, byte ini tidak dipakai dan diberi nilai 0 (seperti yang aku jumpai pada sample latihanku).

Dua byte berikutnya menunjukkan total ukuran datagram dalam satuan bytes. Dalam contoh ini, dua byte tersebut adalah 0030H (atau dalam desimal, 48) yang menunjukkan ukuran datagram IP termasuk header adalah 48 bytes.

Dua byte berikutnya berisi angka identifikasi untuk IP tersebut. Angka ini adalah angka unik dan berperan penting jika datagram mengalami fragmentasi. Komputer penerima akan menggabungkan setiap fragment nantinya berdasarkan angka identifikasi yang sama.

Byte berikutnya berisi beberapa informasi. Pada contoh, nilainya adalah 40H atau 0100 0000. Bit pertama umumnya tidak dipakai. Dua bit berikutnya, menunjukkan nilai flag DF (Don’t Fragment) dan MF (More Fragment). Pada contoh, nilai DF adalah 1, menunjukkan bahwa datagram tidak di-fragmentasi. Jika nilai MF berupa 1, maka datagram ini masih memiliki sub-packet ‘sambungan’ lagi (ter-fragmentasi). Sub-packet yang terakhir akan memiliki MF dengan nilai 0.

Bit ke-empat hingga ke-delapan, 0 0000, digabungkan dengan byte berikutnya, 0000 0000, membuat angka 13-bit yang menunjukkan urutan (offset) setiap fragment. Oleh sebab itu, nilai ini hanya dipakai jika MF bernilai 1. Dengan adanya nilai ini, komputer penerima dapat menggabungkan kembali setiap sub-packet sesuai urutan walaupun sub-packet dikirim dengan urutan yang acak. Nilai maksimum untuk fragment offset adalah 8.191.

Byte berikutnya menunjukkan informasi Time To Live (TTL), yang pada contoh bernilai 80H (atau dalam desimal, 128). Nilai ini menunjukkan berapa lama packet ini dapat berada dalam jaringan sebelum akhirnya dibuang. Dengan demikian, packet IP tidak bebas berkeliaran di jaringan selamanya. Setiap kali packet IP diterima oleh gateway, nilai TTL-nya akan dikurangi. Jika memang packet IP harus dibuang (nilai TTL mencapai 0), maka sebuah pesan harus disampaikan kepada mesin pengirim agar ia tahu apa yang terjadi 🙂 Bayangkan apa yang akan terjadi di Internet tanpa informasi TTL!!! Sebuah packet IP yang memiliki alamat IP tujuan yang salah (mungkin disengaja) akan terus berkeliling dan berkelana didunia internet agar bisa menemukan tujuannya, dan ia akan terus begitu selamanya karena tujuannya memang tidak pernah ada di dunia ini. P.S: Sebagai bahan renungan, TTL manusia umumnya 70-80 tahun, apakah kamu sudah menemukan alamat tujuanmu sebelum TTL-mu mencapai 0 (kosong)?

Byte berikutnya menunjukkan informasi transport protocol yang dipakai. Kode 06H menunjukkan penggunaan TCP. Nilai lain, misalnya, 01H untuk ICMP dan 11H untuk UDP. Dua byte berikutnya adalah nilai checksum (dalam contoh, 0204H).

Empat byte berikutnya, 0AH 0AH ??H ??H adalah alamat IP (IP address) untuk pengirim. Dalam desimal angka tadi akan berupa 10.10.??.?? (angka dengan tanda tanya sengaja tidak dimunculkan :-). Dan begitu juga dengan empat byte berikutnya, 48H 05H 7CH 41H. Itu adalah alamat IP penerima, yang dalam desimal berupa 72.5.124.65.