Sebelum menulis kode jaringan dalam C, kita perlu memahami tiga konsep inti yang dipakai semua protokol modern: host, port, dan protokol transport. Tanpa ketiganya, pemanggilan socket(), bind(), dan connect() terasa seperti mantra yang dihafal tanpa makna. Bab ini membuka section Jaringan dan Protokol dengan menjelaskan model TCP/IP, menunjukkan alamat IP dan port dari sudut pandang kernel, serta memetakan konsep-konsep tersebut ke API C yang akan kita gunakan di bab-bab berikutnya. Kita juga akan menggunakan alat-alat sistem (ip, ss) untuk melihat langsung apa yang sedang terjadi pada interface dan socket di mesin tempat kita bekerja.
Lapisan TCP/IP dan Peran Masing-masing #
Model TCP/IP adalah tumpukan empat lapisan yang menjadi dasar komunikasi jaringan Internet. Setiap lapisan punya tanggung jawab yang berbeda dan independen dari lapisan lain.
| Lapisan | Tanggung Jawab | Contoh Protokol |
|---|---|---|
| Application | Format pesan, semantik aplikasi | HTTP, DNS, SSH, SMTP |
| Transport | Pengiriman end-to-end, keandalan | TCP, UDP |
| Internet | Routing antar jaringan | IPv4, IPv6, ICMP |
| Link | Pengiriman frame antar perangkat | Ethernet, Wi-Fi, loopback |
Kode socket C beroperasi di lapisan Transport. Saat kita memanggil socket(AF_INET, SOCK_STREAM, 0), kernel membuat endpoint transport yang menggunakan protokol TCP di atas IPv4. Lapisan Application (HTTP, DNS, dst.) sepenuhnya tanggung jawab kita sebagai pengembang aplikasi—kernel tidak mengenal HTTP.
Host: Alamat IP dan Loopback #
Host adalah mesin di jaringan, diidentifikasi oleh alamat IP. Pada IPv4, alamat terdiri dari 32 bit yang biasa ditulis sebagai empat oktet desimal (mis. 127.0.0.1, 192.168.1.10). Setiap mesin memiliki interface lo (loopback) dengan alamat 127.0.0.1/8 yang khusus untuk komunikasi ke diri sendiri—tanpa melewati kartu jaringan fisik.
Perintah ip addr show lo menampilkan interface loopback:
ip addr show loOutput yang Dihasilkan:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft foreverBaris inet 127.0.0.1/8 berarti interface lo memiliki alamat IPv4 127.0.0.1 dengan prefix /8 (alamat 127.0.0.0 hingga 127.255.255.255 semuanya loopback). Untuk pengembangan dan testing aplikasi jaringan, loopback sangat berguna karena server dan klien dapat berjalan di mesin yang sama tanpa konfigurasi firewall eksternal.
Port: Pintu Masuk Transport #
Port adalah angka 16-bit (0-65535) yang mengidentifikasi endpoint di dalam sebuah host. Saat paket TCP tiba di kernel, kernel melihat field port tujuan untuk menentukan socket mana yang akan menerima datanya. Tanpa port, sebuah host hanya bisa menjalankan satu server TCP pada satu waktu.
Perintah ss -tlnp menampilkan socket TCP yang sedang listen:
ss -tlnp | head -6Output yang Dihasilkan:
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 *:*Kolom Local Address:Port menunjukkan pasangan IP:port tempat server mendengarkan koneksi. Baris pertama menunjukkan server hanya mendengarkan koneksi dari loopback (127.0.0.1), sementara baris terakhir mendengarkan di semua interface (*:81, IPv4 dan IPv6). Kolom Process biasanya diikuti nama proses bila dijalankan tanpa head dan kita punya hak akses cukup.
Informasi Jaringan dari C dengan getaddrinfo #
Daripada menghafal API terpisah untuk hostname lookup, alamat IPv4, dan IPv6, kita gunakan getaddrinfo. Fungsi ini modern, protocol-agnostic, dan menggantikan gethostbyname yang sudah obsolete. Berikut program C yang menampilkan hostname lokal lalu mencari semua alamat IP yang diasosiasikan dengannya:
/* File: dasar1.c - Tampilkan hostname dan info alamat IP lokal */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
int main(void) {
char host[256];
if (gethostname(host, sizeof(host)) != 0) {
perror("gethostname");
return 1;
}
printf("Hostname lokal : %s\n", host);
/* Cari alamat IP untuk hostname sendiri via getaddrinfo */
struct addrinfo hints, *res, *p;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* IPv4 atau IPv6 */
hints.ai_socktype = SOCK_STREAM; /* TCP */
int rc = getaddrinfo(host, NULL, &hints, &res);
if (rc != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
return 1;
}
char ip_str[INET6_ADDRSTRLEN];
int count = 0;
for (p = res; p != NULL; p = p->ai_next) {
void *addr;
const char *family;
if (p->ai_family == AF_INET) {
struct sockaddr_in *s4 = (struct sockaddr_in *) p->ai_addr;
addr = &(s4->sin_addr);
family = "IPv4";
} else if (p->ai_family == AF_INET6) {
struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) p->ai_addr;
addr = &(s6->sin6_addr);
family = "IPv6";
} else {
continue;
}
inet_ntop(p->ai_family, addr, ip_str, sizeof(ip_str));
printf("Alamat %s [%d] : %s\n", family, ++count, ip_str);
}
freeaddrinfo(res);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o dasar1 dasar1.c
./dasar1Output yang Dihasilkan:
Hostname lokal : c-6acd9659-14810412-33ddf6d3a64e
Alamat IPv4 [1] : 21.0.9.138Mari kita telaah baris-baris kode di atas:
- Baris 11 (
gethostname(host, sizeof(host))): Meminta kernel mengisi bufferhostdengan nama mesin. Fungsi ini tidak butuh socket; ia langsung membaca dariutsnamekernel. - Baris 17-22 (
struct addrinfo hints): Mengonfigurasi filter pencarian.ai_family = AF_UNSPECberarti kita terima baik IPv4 (AF_INET) maupun IPv6 (AF_INET6).ai_socktype = SOCK_STREAMmembatasi ke alamat yang bisa dipakai untuk TCP. - Baris 24 (
getaddrinfo(host, NULL, &hints, &res)): Melakukan resolusi DNS (atau/etc/hostsuntuk nama lokal). Argumen kedua adalah port sebagai string ("80","http", atauNULL). Argumen ketiga adalah filter, argumen keempat adalah linked-list hasil. - Baris 32-46 (loop
for): Iterasi seluruh hasil di linked list. Untuk setiap entri, kita tentukan family (AF_INETatauAF_INET6), lalu ambil pointer ke field alamat yang benar (sin_addruntuk IPv4,sin6_addruntuk IPv6).inet_ntopmengkonversi biner ke string yang dapat dicetak. - Baris 47 (
freeaddrinfo(res)): Wajib dipanggil untuk membebaskan memori yang dialokasikangetaddrinfo. Linked-listresdialokasikan di heap; tanpafreeaddrinfo, terjadi memory leak.
Konversi IP String ke Biner dan Network Byte Order #
Kernel menyimpan alamat IP dalam bentuk biner 32-bit (untuk IPv4), bukan string. Untuk mengisi struct sockaddr_in.sin_addr, kita perlu mengkonversi string "192.168.1.10" ke bentuk biner. Fungsi modern untuk ini adalah inet_pton (presentation to network).
Selain itu, arsitektur x86 menyimpan angka sebagai little-endian, tetapi TCP/IP mensyaratkan big-endian (network byte order). Fungsi htonl (host-to-network-long, 32-bit) dan htons (host-to-network-short, 16-bit) mengkonversi antara dua orde tersebut.
/* File: dasar2.c - Konversi IP string <-> biner dan cek network byte order */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
int main(void) {
/* Konversi "192.168.1.10" ke in_addr (network byte order) */
const char *ip_str = "192.168.1.10";
struct in_addr addr;
if (inet_pton(AF_INET, ip_str, &addr) != 1) {
fprintf(stderr, "inet_pton gagal untuk %s\n", ip_str);
return 1;
}
printf("IP string : %s\n", ip_str);
printf("IP biner (hex) : 0x%08x\n", ntohl(addr.s_addr));
/* Balik ke string */
char buf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &addr, buf, sizeof(buf)) == NULL) {
perror("inet_ntop");
return 1;
}
printf("IP balik ke string: %s\n", buf);
/* Demonstrasikan htons/htonl untuk port */
unsigned short port_host = 8080;
unsigned short port_net = htons(port_host);
printf("\nPort host order : %u\n", port_host);
printf("Port network order: 0x%04x\n", port_net);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o dasar2 dasar2.c
./dasar2Output yang Dihasilkan:
IP string : 192.168.1.10
IP biner (hex) : 0xc0a8010a
IP balik ke string: 192.168.1.10
Port host order : 8080
Port network order: 0x901fPada output di atas, 0xc0a8010a adalah representasi hex dari 192.168.1.10 (192=0xc0, 168=0xa8, 1=0x01, 10=0x0a). Saat di-print dengan ntohl (network-to-host-long), kernel mengkonversi big-endian kembali ke little-endian sehingga printf menampilkannya sebagai satu angka 0xc0a8010a yang konsisten dengan representasi string. Untuk port 8080 (desimal), representasi hex host-order adalah 0x1f90, sedangkan network-order menjadi 0x901f—byte terbalik karena htons menukar urutan.
Layanan Terkenal dan /etc/services #
Port 0-1023 disebut well-known ports dan dikelola oleh IANA. Sebagai contoh, port 80 untuk HTTP, port 22 untuk SSH, port 443 untuk HTTPS. Linux menyimpan pemetaan nama layanan ke port di file /etc/services. Fungsi C getservent membaca file ini dan mengembalikan struct servent yang berisi nama, port, dan protokol.
/* File: dasar3.c - Daftar port layanan terkenal via getservent */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <netdb.h>
int main(void) {
/* Tampilkan beberapa layanan terkenal dari /etc/services */
struct servent *sv;
setservent(0);
int count = 0;
printf("%-12s %-8s %s\n", "Nama", "Port", "Protokol");
printf("%-12s %-8s %s\n", "----", "----", "--------");
while ((sv = getservent()) != NULL) {
printf("%-12s %-8d %s\n", sv->s_name, ntohs(sv->s_port), sv->s_proto);
if (++count >= 8) break;
}
endservent();
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o dasar3 dasar3.c
./dasar3Output yang Dihasilkan:
Nama Port Protokol
---- ---- --------
tcpmux 1 tcp
echo 7 tcp
echo 7 udp
discard 9 tcp
discard 9 udp
systat 11 tcp
daytime 13 tcp
daytime 13 udpField s_port di struct servent sudah dalam network byte order, sehingga kita wajib memanggil ntohs untuk menampilkan angka port dalam bentuk yang bisa dibaca. Sebagai contoh, layanan echo ada pada port 7 baik untuk TCP maupun UDP—sama namanya, tetapi protokol berbeda.