Pengembangan aplikasi jaringan memerlukan teknik testing yang berbeda dari pengembangan aplikasi biasa. Bug jaringan sering hanya muncul di kondisi tertentu—koneksi yang ditolak, paket yang hilang, latensi tinggi, atau respons yang tidak sesuai format. Bab ini memperkenalkan tiga alat fundamental untuk testing aplikasi jaringan: netcat (nc) untuk pengujian manual, tcpdump/wireshark untuk capture paket, dan klien C kustom sebagai pengganti nc ketika tidak tersedia. Kita juga akan melihat ss -tn untuk inspeksi koneksi aktif dan openssl s_client untuk debugging TLS.

Tiga Tingkat Testing Aplikasi Jaringan #

TingkatAlatYang Diuji
Manualnc, telnet, klien C kustomProtokol aplikasi dari sisi user
Capturetcpdump, wireshark, ssPaket aktual di kabel
IntegrationUnit test dengan mock serverLogika bisnis aplikasi

Tabel di atas adalah pendekatan berlapis. Tingkat manual cepat untuk debugging awal—kita bisa ketik perintah dan lihat respons. Tingkat capture menunjukkan apa yang sebenarnya terjadi di kabel (termasuk header TCP/IP yang tidak terlihat di aplikasi). Tingkat integration mengotomatiskan pengujian untuk CI/CD pipeline.

nc: Pengujian Manual Protokol Teks #

nc (netcat) adalah alat serbaguna untuk membaca dan menulis data ke koneksi TCP/UDP. Berikut cara menguji echo server dari bab Client-Server dengan nc:

bash
# Terminal 1: jalankan echo server
./cs_server &

# Terminal 2: jalankan nc sebagai klien
nc 127.0.0.1 9093
PING
PING
echo ini teks
echo ini teks
QUIT
QUIT
^D

Setiap baris yang kita ketik dikirim ke server, server echo balik, dan kita melihat responsnya. ^D (Ctrl+D) menutup stdin yang mengirim EOF ke nc, yang pada gilirannya menutup koneksi TCP.

Pada beberapa sistem, nc tidak terinstall. Sebagai alternatif, kita bisa menulis klien C sederhana yang berfungsi seperti nc—membaca stdin, mengirim ke socket, dan menampilkan respons ke stdout. Berikut implementasinya:

c
/* File: rawtcp.c - Klien TCP sederhana (pengganti nc untuk testing) */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netdb.h>

/* Penggunaan: ./rawtcp <host> <port>
 * Membaca stdin, kirim ke server, cetak respons ke stdout
 */
int main(int argc, char **argv) {
    if (argc < 3) {
        fprintf(stderr, "Penggunaan: %s <host> <port>\n", argv[0]);
        return 1;
    }
    const char *host = argv[1];
    const char *port = argv[2];

    struct addrinfo hints = {0}, *res;
    hints.ai_family   = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    int rc = getaddrinfo(host, port, &hints, &res);
    if (rc != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
        return 1;
    }
    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (fd < 0) { perror("socket"); return 1; }
    if (connect(fd, res->ai_addr, res->ai_addrlen) != 0) {
        perror("connect"); close(fd); return 1;
    }
    freeaddrinfo(res);
    fprintf(stderr, "[rawtcp] tersambung ke %s:%s\n", host, port);

    /* Baca stdin -> kirim ke socket, baca socket -> cetak stdout */
    char buf[1024];
    fd_set rfds;
    while (1) {
        FD_ZERO(&rfds);
        FD_SET(STDIN_FILENO, &rfds);
        FD_SET(fd, &rfds);
        int nfds = (fd > STDIN_FILENO ? fd : STDIN_FILENO) + 1;
        if (select(nfds, &rfds, NULL, NULL, NULL) < 0) {
            if (errno == EINTR) continue;
            perror("select"); break;
        }
        if (FD_ISSET(STDIN_FILENO, &rfds)) {
            ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
            if (n <= 0) { shutdown(fd, SHUT_WR); }
            else send(fd, buf, (size_t)n, 0);
        }
        if (FD_ISSET(fd, &rfds)) {
            ssize_t n = recv(fd, buf, sizeof(buf), 0);
            if (n <= 0) break;
            fwrite(buf, 1, (size_t)n, stdout);
            fflush(stdout);
        }
    }
    close(fd);
    return 0;
}

Compile dan uji:

bash
gcc -std=c11 -Wall -Wextra -o rawtcp rawtcp.c
./cs_server &
sleep 0.3
printf "PING\nraw-test-2\nQUIT\n" | ./rawtcp 127.0.0.1 9093
wait

Output yang Dihasilkan (stdout klien):

text
raw-test-1
raw-test-2
QUIT

Output yang Dihasilkan (server):

text
[SERVER] echo server di 127.0.0.1:9093
[SERVER] klien terhubung
[SERVER] echo: raw-test-1
[SERVER] echo: raw-test-2
[SERVER] echo: QUIT
[SERVER] klien memutus (0)

Mari kita telaah baris-baris kode di atas:

  1. Baris 36-37 (select(nfds, &rfds, ...)): I/O multiplexing—select memblokir hingga salah satu dari stdin atau socket siap dibaca. Tanpa select, kita harus memilih satu sumber: bila hanya loop baca stdin, kita tidak bisa melihat respons server tepat waktu. Dengan select, klien bisa menangani kedua arah secara bersamaan dalam satu thread.
  2. Baris 41-45 (FD_ISSET(STDIN_FILENO, ...)): Bila stdin siap dibaca, baca byte, lalu send ke socket. Bila read mengembalikan 0 (EOF, mis. Ctrl+D atau pipe tertutup), panggil shutdown(fd, SHUT_WR) untuk mengirim FIN ke server—memberitahu “saya selesai mengirim, tapi masih mau menerima”.
  3. Baris 46-52 (FD_ISSET(fd, ...)): Bila socket siap dibaca, recv data lalu cetak ke stdout. Bila recv mengembalikan 0, server telah menutup koneksi—keluar dari loop. Bila -1, terjadi error—juga keluar.
  4. Pola ini disebut “bidirectional relay”: aplikasi menghubungkan dua stream (stdin ke socket, socket ke stdout) tanpa buffering. Pola yang sama dipakai oleh nc, socat, dan banyak alat testing jaringan lain.

ss: Inspeksi Koneksi Aktif #

ss (socket statistics) adalah alat modern yang menggantikan netstat. Untuk melihat semua koneksi TCP aktif:

bash
ss -tn | head -5

Output yang Dihasilkan:

text
State Recv-Q Send-Q       Local Address:Port       Peer Address:Port 
ESTAB 0      0               21.0.9.138:19006          21.0.0.1:52974
ESTAB 0      0               21.0.9.138:42946     10.243.55.100:3306 
ESTAB 0      0               21.0.9.138:43366     10.243.55.100:3306 
ESTAB 0      0               21.0.9.138:19005          21.0.0.1:49124

Output di atas menunjukkan beberapa koneksi ESTAB (established). Kolom Local Address:Port adalah sisi koneksi di mesin ini, Peer Address:Port adalah sisi remote. Baris kedua dan ketiga menunjukkan koneksi ke 10.243.55.100:3306—port 3306 adalah MySQL default, jadi kemungkinan ini adalah koneksi database.

Untuk melihat socket yang listening saja (server yang menunggu koneksi):

bash
ss -tlnp | head -6

Output yang Dihasilkan:

text
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      2048       127.0.0.1:12600      0.0.0.0:*          
LISTEN 0      128        127.0.0.1:19001      0.0.0.0:*          
LISTEN 0      1024         0.0.0.0:19005      0.0.0.0:*          
LISTEN 0      128          0.0.0.0:19006      0.0.0.0:*          
LISTEN 0      4096               *:81               *:*          

ss berguna untuk memverifikasi bahwa server Anda benar-benar mendengarkan di port yang diharapkan, dan untuk debugging masalah “port already in use” (lihat baris dengan port yang sama—satu proses telah memegangnya).

tcpdump dan Wireshark: Capture Paket #

Untuk melihat paket aktual yang mengalir di kabel, tcpdump adalah alat command-line standar. wireshark adalah GUI yang lebih kaya fitur tetapi membutuhkan display server. Penggunaan dasar:

bash
# Capture semua paket TCP di interface lo (loopback) port 9093
sudo tcpdump -i lo -n tcp port 9093

Output khas (dipotong untuk kejelasan):

text
13:42:11.123456 IP 127.0.0.1.43210 > 127.0.0.1.9093: Flags [S], seq 123456, win 65495, [tcp sum ok], length 0
13:42:11.123457 IP 127.0.0.1.9093 > 127.0.0.1.43210: Flags [S.], seq 789012, ack 123457, win 65483, [tcp sum ok], length 0
13:42:11.123458 IP 127.0.0.1.43210 > 127.0.0.1.9093: Flags [.], ack 1, win 512, length 0
13:42:11.124001 IP 127.0.0.1.43210 > 127.0.0.1.9093: Flags [P.], seq 1:6, ack 1, win 512, length 5

Empat baris pertama adalah TCP three-way handshake: klien mengirim SYN (Flags [S]), server membalas SYN-ACK (Flags [S.]), klien mengirim ACK (Flags [.]). Baris keempat menunjukkan klien mengirim data 5 byte (length 5) dengan flag PSH-ACK (Flags [P.]). Inilah yang sebenarnya terjadi di tingkat TCP ketika aplikasi memanggil connect() + send().

openssl s_client: Debug TLS #

Untuk menguji server HTTPS atau protokol berbasis TLS lainnya, openssl s_client adalah alat yang powerful. Alih-alih hanya konektivitas TCP, alat ini menampilkan seluruh certificate chain dan hasil verifikasi:

bash
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null | head -20

Output yang Dihasilkan:

text
Connecting to 172.66.147.243
depth=3 C=US, O=SSL Corporation, CN=SSL.com TLS ECC Root CA 2022
verify return:1
depth=2 C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
verify return:1
depth=1 C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
verify return:1
depth=0 CN=example.com
verify return:1
CONNECTED(00000003)
---
Certificate chain
 0 s:CN=example.com
   i:C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3

Output di atas menampilkan certificate chain dari leaf certificate (CN=example.com) hingga root CA. verify return:1 di setiap depth menandakan verifikasi sukses pada level tersebut. Bila sertifikat kedaluwarsa, self-signed, atau hostname tidak cocok, verify return:0 akan muncul pada level yang bermasalah.

Strategi Testing Terstruktur #

Untuk aplikasi production, pengujian manual tidak cukup. Berikut strategi berlapis yang direkomendasikan:

  1. Unit test dengan mock server: tulis server mock yang merespons sesuai skenario, jalankan klien dalam test framework (mis. cmocka, Criterion). Setiap skenario (sukses, timeout, koneksi ditolak) punya test case sendiri.
  2. Integration test dengan container: jalankan server dan klien di container Docker terpisah, verifikasi end-to-end. Docker Compose cocok untuk ini.
  3. Chaos testing: simulasikan kegagangan jaringan dengan tc (traffic control) atau toxiproxy—drop paket, tambahkan latensi, batasi bandwidth.
  4. Fuzzing: kirim input acak atau malformed ke parser protokol (mis. dengan libFuzzer atau `AFL++). Bab-bab di section debugging membahas fuzzing lebih dalam.