socket: Too many open files disusul rentetan kegagalan alokasi memori.
Masalah ini bukan disebabkan oleh serangan DDoS volume tinggi, melainkan sebuah anomali arsitektur DNS klasik yang sangat destruktif: circular glue record dependency. Ketika dua domain saling menunjuk nameserver satu sama lain tanpa glue record independen di level top-level domain (TLD), resolver dapat terjebak dalam infinite resolution loop. Masalah menjadi bencana sistemik saat state machine resolver terus mengalokasikan socket file descriptor baru untuk mengejar dependensi yang mustahil diselesaikan, mengunci buffer kernel, dan akhirnya melumpuhkan seluruh kapasitas I/O server.
Jebakan Sibling-Domain dan Delusi Hierarki DNS
alpha.id mendelegasikan otoritas ke ns1.alpha.id, registry TLD .id wajib menyertakan glue record—yaitu record A/AAAA yang memetakan ns1.alpha.id langsung ke IP address-nya di dalam respon referral. Tanpa glue record ini, resolver tidak akan pernah tahu ke mana harus mengirim kueri karena untuk mengetahui IP ns1.alpha.id, ia harus bertanya pada ns1.alpha.id itu sendiri.
Petaka dimulai saat arsitektur multi-domain dikonfigurasi dengan relasi sirkular lintas sibling zone: domain alpha.id mendelegasikan NS ke ns1.bravo.id, sementara bravo.id mendelegasikan NS ke ns1.alpha.id. Selama IP kedua nameserver tersebut masih tersimpan di dalam memory cache resolver, seluruh sistem tampak berjalan mulus tanpa cacat.
Namun, DNS bukanlah database statis. Ketika terjadi event flap pada BGP routing otoritatif atau saat TTL (Time To Live) record kadaluwarsa serentak, resolver dipaksa melakukan cold resolution. Resolver bertanya ke TLD .id untuk mencari IP ns1.bravo.id, tetapi TLD hanya memberikan referral bahwa otoritas berada di ns1.alpha.id. Resolver kemudian mencoba menyelesaikan ns1.alpha.id, yang kembali merujuk ke ns1.bravo.id. Keduanya berada di luar bailiwick masing-masing dari perspektif zona anak, menciptakan dependensi sirkular tertutup tanpa titik awal resolusi yang valid.
Anatomi Iterator Deadlock di Level State Machine
mesh_state dan iterator module.
Ketika sebuah kueri masuk dari client untuk record di bawah alpha.id, iterator mendeteksi bahwa ia membutuhkan alamat dari ns1.bravo.id. Alih-alih memblokir thread pekerja, resolver membuat sub-query independen untuk mencari ns1.bravo.id. Sub-kueri ini kemudian dieksekusi dan menemukan bahwa ia membutuhkan ns1.alpha.id, yang kembali melahirkan sub-kueri baru.
Meskipun resolver modern memiliki proteksi kedalaman rekursi (misalnya batas maksimal kedalaman kueri 16 hingga 32 iterasi), proteksi ini dirancang dengan asumsi dependensi pohon bercabang, bukan grafik tertutup. Dalam trafik produksi sebesar 40.000 QPS, setiap kueri client yang gagal akan melahirkan puluhan sub-kueri ke dalam antrean iterator. Ribuan task iterator ini berada dalam status waiting secara bersamaan, menunggu respon jaringan dari upstream authoritative server yang tidak akan pernah datang.
Petaka di Kernel Space: Dari Socket Descriptors ke Buffer Starvation
socket(AF_INET, SOCK_DGRAM, ...). Socket ini kemudian didaftarkan ke event loop menggunakan mekanisme epoll. Karena query target tidak pernah dapat diselesaikan, socket-socket ini tidak ditutup secara instan; mereka harus bertahan di memory menunggu batas query timeout (biasanya antara 4 hingga 10 detik tergantung konfigurasi backoff retry).
TEXT
[Client Query: api.alpha.id]
|
v
[Unbound mesh_state] --(Spawn Sub-query)--> Socket FD #1024 (Awaiting ns1.bravo.id)
| |
v v
[Iter Resolve Loop] --(Spawn Sub-query)--> Socket FD #1025 (Awaiting ns1.alpha.id)
| |
(40k QPS) v
| [Kernel epoll: FD Exhaustion]
v |
[EMFILE: Resource Exhausted] <----------------------+nofile (Number of Open Files) pada level proses maupun fs.file-max pada level kernel. Saat syscall socket() mengembalikan eror EMFILE (Too many open files), resolver mengalami degradasi total: ia tidak lagi mampu membuka socket baru untuk melayani kueri domain yang benar-benar sehat.
Dampaknya tidak berhenti pada kehabisan file descriptor. Setiap socket UDP yang dialokasikan kernel mengonsumsi memory untuk receive (rmem) dan transmit buffer (wmem) melalui struktur sk_buff. Alokasi kernel memory yang masif dan tidak segera dibebaskan ini menyebabkan memory pinning. Resolver tidak dapat membebaskan pointer memory state machine sebelum timeout terlampaui. Seperti yang pernah kita bahas dalam mitigasi alokasi memory resolver di cgroups v2, tekanan buffer socket UDP yang tidak terkelola akan berujung pada silent packet drop dan pembantaian proses oleh OOM Killer.
Dilema Otoritas: Child vs. Parent Trust Model
.id). Record ini memiliki status kelayakan rendah (biasanya diklasifikasikan sebagai additional data non-authoritative). Namun, protokol DNS menginstruksikan resolver untuk mempercayai data otoritatif dari child zone di atas data referral parent zone begitu child zone berhasil dihubungi.
Jika pengelola zona mempublikasikan NS record di child zone yang berbeda dengan glue record di parent zone (misalnya akibat kesalahan automasi CI/CD Terraform atau migrasi DNS registrar parsial), resolver akan menimpa cache valid yang dimilikinya dengan data baru yang rusak. Begitu cache lama kadaluwarsa, resolver membuang data glue record parent dan mencoba mempercayai skema delegasi baru yang ternyata sirkular. Resolver kehilangan jalur kembali ke jangkar IP yang benar, mengunci dirinya sendiri dalam siklus kegagalan resolusi.
Mitigasi Arsitektur dan Hardening Resolver
1. Pembatasan Sub-query Target Fetching
target-fetch-policy. Mengatur policy ini secara konservatif mencegah state machine melahirkan kueri anak secara agresif saat terjadi kegagalan pencarian glue record:
YAML
server:
# Batasi kedalaman iterasi pengambilan alamat target
target-fetch-policy: "0 0 0 0 0"
# Batasi alokasi socket UDP out-of-band
outgoing-num-tcp: 100
outgoing-num-udp: 1024
infra-host-ttl: 60
# Hardening alokasi sub-kueri per request
max-sent-count: 16target-fetch-policy ke mode pasif, resolver hanya akan mencoba menyelesaikan nameserver menggunakan data yang ada atau kueri minimal, alih-alih melakukan rekursi liar yang menghabiskan socket descriptor.
2. Observabilitas Kernel Space via eBPF
Too many open files muncul, resolver biasanya sudah mati suri. Pendekatan yang jauh lebih baik adalah memantau lonjakan pembuatan socket berumur pendek yang tidak menerima respon menggunakan eBPF.
Dengan mengaitkan program eBPF ke tracepoint sock:inet_sock_set_state atau memantau syscall sys_enter_socket, tim SRE dapat memicu alert otomatis saat tingkat kueri iterator yang tertahan (outstanding iterative queries) melampaui ambang deviasi standar normal. Jika laju pembuatan socket UDP meningkat tajam sementara metrik throughput paket keluar-masuk stagnan, ini merupakan indikator kuat terjadinya iterator deadlock akibat circular delegation.
Sistem DNS recursive bukanlah pipa pasif yang kebal terhadap kegagalan arsitektur upstream. Tanpa pembatasan ketat pada kedalaman state machine iterator dan isolasi alokasi file descriptor, circular glue record membuktikan bahwa kesalahan konfigurasi pada dua domain antah berantah di internet dapat melumpuhkan resolver enterprise berkapasitas puluhan ribu QPS dalam hitungan detik."
],
"uncoveredConcepts": [
{
"concept": "DNS Bailiwick Algorithm & Credibility Matrix",
"suggestedTitle": "Membedah Algoritma DNS Bailiwick: Menakar Integritas Trust Matrix dan Proteksi Poisoning Cache",
"tag": "DevOps"
},
{
"concept": "Linux Epoll Starvation Under Network Wait Latency",
"suggestedTitle": "Menghindari Starvation Epoll Event Loop pada Desain Socket Jaringan Berlatensi Tinggi",
"tag": "DevOps"
},
{
"concept": "eBPF Socket Lifecycle Tracing",
"suggestedTitle": "Tracing Lifecycle Socket Kernel Linux Menggunakan eBPF untuk Deteksi Dini Leak FD",
"tag": "DevOps"
}
]
}