Pertanyaan “di port apa layanan ini berjalan, dan siapa yang memakai port itu?” adalah pertanyaan harian administrator — muncul saat memasang server web, saat aplikasi menolak menyala karena port dipegang proses lain, atau saat memeriksa apakah backdoor tidak mendengarkan di port yang tidak kita kenal. Selama dua dekade jawabannya adalah netstat, tapi alat itu kini berstatus legacy: paketnya, net-tools, sudah lama tidak dikembangkan dan di banyak distribusi tidak lagi terpasang bawaan. Pewarisnya adalah ss dari koleksi iproute2 — alat yang kita pakai berulang kali di Chapter Network Diagnostics dan kini dibedah tuntas.

Dari netstat ke ss #

Perbedaan keduanya bukan sekadar nama. netstat bekerja dengan membaca berkas di bawah /proc/net/ baris demi baris lalu memformat ulang isinya — pendekatan yang menjadi lambat di server dengan puluhan ribu koneksi. ss mengambil data langsung dari kernel lewat antarmuka netlink: satu dialog kernel-userspace yang juga dipakai perintah ip. Hasilnya lebih cepat dan lebih kaya — ss bisa menampilkan state koneksi, filter alamat, sampai proses pemilik socket dalam satu panggilan. Keduanya masih hidup berdampingan di banyak sistem tua, jadi kita perlu membaca dua-duanya.

Anatomi Opsi ss #

ss dibangun dari gabungan opsi satu huruf yang saling dirangkai bebas:

OpsiMenampilkanCatatan
-tsocket TCPpadanannya -u untuk UDP
-usocket UDPtanpa koneksi, UDP hanya tampak sebagai UNCONN
-lyang sedang listening sajatanpa opsi ini justru koneksi aktif yang tampil
-nalamat dan port sebagai angkamencegah pencarian nama DNS yang lambat
-pproses pemilik socketnama proses milik user lain butuh root
-alistening dan tidak listeninggabungan keduanya

Kombinasi yang paling sering diketik adalah ss -tln: koneksi TCP yang mendengarkan, dalam bentuk angka. Keluaran lengkap di mesin sesungguhnya panjang dan berbeda-beda, jadi seperti biasa kita berlatih dengan server mini di loopback — lalu membandingkan langsung dengan netstat pada server yang sama:

bash
printf 'halo' > index.html
python3 -m http.server 8765 --bind 127.0.0.1 >/dev/null 2>&1 &
server=$!
for i in 1 2 3 4 5; do curl -s -o /dev/null 127.0.0.1:8765/ && break; sleep 0.2; done
netstat -tln | grep 8765
ss -tln | grep 8765
kill "$server"

Output yang Dihasilkan:

text
tcp        0      0 127.0.0.1:8765          0.0.0.0:*               LISTEN     
LISTEN 0      5          127.0.0.1:8765       0.0.0.0:*   

Dua baris hasil itu adalah informasi yang sama dikemas dua generasi: netstat mencantumkan protokol di kolom pertama dan status LISTEN di kolom terakhir, sedangkan ss meletakkan state di depan. Bagian pentingnya identik — 127.0.0.1:8765 sebagai alamat lokal, 0.0.0.0:* sebagai sisi jauh yang masih kosong untuk socket yang mendengarkan.

Melihat Proses di Balik Socket #

Opsi -p menjawab pertanyaan berikutnya setelah port ditemukan: proses mana yang memegangnya? Karena kolom itu memuat PID yang berubah setiap eksekusi, PID pada contoh di bawah dinormalisasi agar stabil — di terminal pembaca, angkanya akan berbeda-beda:

bash
printf 'halo' > index.html
python3 -m http.server 8765 --bind 127.0.0.1 >/dev/null 2>&1 &
server=$!
for i in 1 2 3 4 5; do curl -s -o /dev/null 127.0.0.1:8765/ && break; sleep 0.2; done
ss -tlnp | grep 8765 | sed 's/pid=[0-9]*/pid=N/'
ss -tln '( sport = :8765 )'
kill "$server"

Output yang Dihasilkan:

text
LISTEN 0      5          127.0.0.1:8765       0.0.0.0:*    users:(("python3",pid=N,fd=3))
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      5          127.0.0.1:8765      0.0.0.0:*   

Baris pertama menampilkan kolom users:(("python3",pid=N,fd=3)) — nama program, PID, dan file descriptor socket itu. Tanpa root, ss hanya sanggup menemukan proses milik user sendiri; socket milik user lain tetap tampil tanpa kolom users, jadi jangan bingung bila kolom itu menghilang saat memeriksa layanan sistem. Baris kedua memperkenalkan filter state alamat: seluruh perintah setelah daftar opsi adalah ekspresi filter, dan sport = :8765 berarti “socket TCP yang mendengarkan di port lokal 8765” — baris judul kolom ikut tercetak karena filter dipakai.

Socket UDP #

UDP tidak mengenal koneksi, sehingga di dunia ss socket UDP yang menunggu paket berstatus UNCONN (unconnected) — siap menerima dari siapa saja. Server HTTP mini tadi berbasis TCP, jadi untuk UDP kita perlu pendengar UDP sungguhan; tiga baris Python cukup untuk itu:

bash
python3 -c '
import socket, time
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("127.0.0.1", 8899))
time.sleep(30)
' >/dev/null 2>&1 &
pendengar=$!
for i in 1 2 3 4 5; do ss -uln | grep -q 8899 && break; sleep 0.2; done
ss -uln | grep 8899
kill "$pendengar"

Output yang Dihasilkan:

text
UNCONN 0      0          127.0.0.1:8899      0.0.0.0:*   

Kombinasi -uln dibaca: socket UDP (-u), yang mendengarkan (-l), dengan alamat numerik (-n). Satu baris UNCONN pada 127.0.0.1:8899 itulah seluruh jejak pendengar UDP — tidak ada kolom state LISTEN, karena memang tidak ada sesi yang didengarkan, hanya pintu yang terbuka untuk datagram.

Koneksi Aktif dan Filter Lanjutan #

Dua pemeriksaan di atas berkutat pada socket yang mendengarkan; separuh kehidupan ss yang lain adalah koneksi yang sedang berjalan. Filter state established menyaringnya, dan bisa dirangkai dengan filter alamat seperti dst 192.0.2.0/24 untuk hanya melihat koneksi menuju satu subnet. Masalahnya, koneksi yang layak diperhatikan biasanya berumur pendek — server HTTP kecil menutup kuncinya begitu balasan selesai. Karena itu server uji di bawah sengaja berperilaku seperti backend yang sibuk: koneksi diterima, balasan ditunda, sehingga koneksi established bertahan cukup lama untuk diamati:

bash
python3 -c '
import socket, time
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("127.0.0.1", 8765))
s.listen(5)
while True:
    koneksi, _ = s.accept()
    time.sleep(30)
' >/dev/null 2>&1 &
server=$!
curl -s --max-time 3 --local-port 4242 -o /dev/null 127.0.0.1:8765/ &
klien=$!
sleep 1
ss -Htn state established '( dport = :8765 )' | awk '{print $1, $4, $5}'
kill "$klien" "$server"

Output yang Dihasilkan:

text

Mari kita bedah baris demi baris:

  1. Baris 1-11 (python3 -c '...' &): server TCP mini — socket dibuat, diikat ke 127.0.0.1:8765, lalu masuk lingkar accept(); setiap koneksi yang masuk diterima lalu ditahan 30 detik tanpa dibalas, persis backend yang sedang memproses permintaan lama.
  2. Baris 13 (curl ... --max-time 3 ... &): klien menyambung di latar belakang dan sabar menunggu balasan paling lama tiga detik; --local-port 4242 mematok port sumber koneksi agar keluarannya deterministik.
  3. Baris 15 (ss -Htn state established ...): menyaring koneksi established tujuan port 8765; -H membuang baris judul, dan awk memilih kolom state beserta kedua alamat — kolom antrian Recv-Q/Send-Q sengaja dilewatkan karena berubah-ubah seiring trafik.
  4. Baris 16 (kill "$klien" "$server"): menghentikan klien dan server — satu baris, dua proses.

Hasilnya satu baris ESTAB 127.0.0.1:4242 127.0.0.1:8765: state koneksi, sisi klien, lalu sisi server — dua socket yang sedang berjabat tangan lewat loopback. Pola ini melengkapi pemeriksaan port di Chapter Network Diagnostics : dari lihat, kita bisa naik ke tanya.

Dengan socket yang terbuka kini mudah dijualar, pertanyaan berikutnya biasanya berbunyi: bagaimana menjadi pihak yang menyambung — mengambil berkas dari server, memanggil API, menyalin salinan situs? Dua alat klasik menunggu di Chapter berikutnya: curl dan wget.

Referensi lanjut: halaman manual ss(8) untuk daftar state dan filter lengkapnya, serta netstat(8) untuk membaca mesin lama yang masih memakainya.