Hampir setiap aplikasi berorientasi jaringan menyimpan alamat IP: log keamanan mencatat sumber setiap permintaan, sistem akses menyimpan daftar jaringan terpercaya, dan aplikasi absensi menyimpan alamat MAC perangkat karyawan. Kalau alamat-alamat itu disimpan sebagai text, kita kehilangan tiga hal sekaligus: validasi otomatis, perbandingan yang benar, dan operator jaringan seperti “apakah IP ini berada di dalam subnet 10.0.0.0/8?”. PostgreSQL menjawabnya dengan tiga tipe khusus — inet, cidr, dan macaddr — yang kita bedah tuntas di chapter ini.

Tiga Tipe Alamat Jaringan #

Ketiga tipe ini menyimpan data yang tampak seperti teks, tetapi disimpan dan diperlakukan sebagai nilai biner terstruktur. Ringkasan karakternya:

TipeUkuranMenyimpanContoh nilai
inet7 atau 19 byteIPv4/IPv6 host, netmask opsional192.168.1.5, 192.168.1.5/24
cidr7 atau 19 byteIPv4/IPv6 jaringan192.168.1.0/24
macaddr6 bytealamat MAC08:00:2b:01:02:03
macaddr88 bytealamat MAC format EUI-6408:00:2b:01:02:03:04:05

Perbedaan inet dan cidr sering membingungkan pemula, jadi mari kita luruskan sejak awal: inet menjawab “alamat siapa”, sedangkan cidr menjawab “jaringan yang mana”. Nilai inet boleh menulis netmask setelah garis miring (192.168.1.5/24), tetapi yang disimpan tetap satu host. Nilai cidr mewakili jaringan secara utuh, sehingga bagian host-nya wajib nol — 192.168.1.0/24, bukan 192.168.1.5/24.

Perbedaan perilaku ketiga tipe paling cepat dipahami lewat contoh. File berikut menyisipkan nilai ke masing-masing tipe dan memperlihatkan bagaimana server menyimpannya kembali:

sql tipe-dasar.sql
-- inet menyimpan satu host; netmask opsional setelah garis miring
SELECT '192.168.1.5'::inet AS host_tanpa_netmask;

SELECT '192.168.1.5/24'::inet AS host_dengan_netmask;

-- inet juga menerima IPv6
SELECT '2001:db8::1'::inet AS ipv6;

-- cidr menyimpan jaringan: bagian host wajib nol
SELECT '192.168.1.0/24'::cidr AS jaringan_v4;

SELECT '2001:db8::/32'::cidr AS jaringan_v6;

-- macaddr menyimpan alamat MAC 6 byte, macaddr8 untuk EUI-64 (8 byte)
SELECT '08:00:2b:01:02:03'::macaddr AS mac_enam_byte;

SELECT '08:00:2b:01:02:03:04:05'::macaddr8 AS mac_delapan_byte;

Output yang Dihasilkan:

text
 host_tanpa_netmask 
--------------------
 192.168.1.5
(1 row)

 host_dengan_netmask 
---------------------
 192.168.1.5/24
(1 row)

    ipv6     
-------------
 2001:db8::1
(1 row)

  jaringan_v4   
----------------
 192.168.1.0/24
(1 row)

  jaringan_v6  
---------------
 2001:db8::/32
(1 row)

   mac_enam_byte   
-------------------
 08:00:2b:01:02:03
(1 row)

    mac_delapan_byte     
-------------------------
 08:00:2b:01:02:03:04:05
(1 row)

Tiga hal penting dari hasil tersebut. Pertama, inet tanpa netmask ditampilkan apa adanya (192.168.1.5) — secara implisit netmasknya /32, artinya tepat satu host. Kedua, inet dan cidr sama-sama menerima IPv6 tanpa sintaks tambahan; tipe yang sama menampung kedua versi, dan fungsi family() akan membedakannya nanti. Ketiga, macaddr menerima berbagai gaya penulisan (08:00:2b:..., 08002b-010203, 0800.2b01.0203) dan menormalkannya ke satu bentuk standar 08:00:2b:01:02:03 — keunggulan klasik tipe khusus dibanding kolom text yang menyimpan apa adanya gaya masukannya.

Tabel Log dengan Kolom inet #

Kegunaan nyata paling mudah dilihat pada tabel log firewall. Kolom sumber bertipe inet menyimpan IP sumber setiap kejadian, lalu fungsi-fungsi pengurai langsung bekerja pada kolom tersebut:

sql tabel-log.sql
CREATE TABLE log_firewall (
    id     integer PRIMARY KEY,
    waktu  timestamp NOT NULL,
    sumber inet NOT NULL,
    aksi   text NOT NULL
);

INSERT INTO log_firewall (id, waktu, sumber, aksi) VALUES
    (1, '2026-03-01 09:15:00', '10.0.2.15',    'IZIN'),
    (2, '2026-03-01 09:16:30', '203.0.113.7',  'TOLAK'),
    (3, '2026-03-01 09:17:00', '192.168.1.23', 'IZIN'),
    (4, '2026-03-01 09:18:45', '172.16.5.9',   'IZIN'),
    (5, '2026-03-01 09:20:10', '203.0.113.99', 'TOLAK');

SELECT id, sumber,
       host(sumber)   AS ip_murni,
       family(sumber) AS versi_ip,
       aksi
FROM log_firewall
ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 5
 id |    sumber    |   ip_murni   | versi_ip | aksi  
----+--------------+--------------+----------+-------
  1 | 10.0.2.15    | 10.0.2.15    |        4 | IZIN
  2 | 203.0.113.7  | 203.0.113.7  |        4 | TOLAK
  3 | 192.168.1.23 | 192.168.1.23 |        4 | IZIN
  4 | 172.16.5.9   | 172.16.5.9   |        4 | IZIN
  5 | 203.0.113.99 | 203.0.113.99 |        4 | TOLAK
(5 rows)

Dua fungsi bekerja di query penutup. host(sumber) mengupas netmask dan mengembalikan alamat murni — berguna saat kolomnya menyimpan nilai ber-netmask tetapi laporan hanya butuh IP-nya. family(sumber) mengembalikan 4 atau 6 sesuai versi IP, sehingga migrasi bertahap ke IPv6 bisa dipantau dengan query sederhana. Perhatikan juga bahwa lima baris log berasal dari jaringan berbeda-beda; di bagian berikutnya kita menguji keanggotaan IP-IP tersebut terhadap daftar jaringan terpercaya.

Operator Keanggotaan dan Tumpang Tindih #

Kekuatan sesungguhnya tipe jaringan ada pada operatornya. Empat operator berikut adalah yang paling sering dipakai:

OperatorMaknaContoh yang bernilai benar
<<IP/jaringan kiri terkandung di kanan'192.168.1.5'::inet << '192.168.1.0/24'::cidr
<<=terkandung atau sama persis'203.0.113.7'::inet <<= '203.0.113.7/32'::cidr
>> / >>=kebalikan: kanan terkandung di kiri'192.168.1.0/24'::cidr >> '192.168.1.5'::inet
&&kedua jaringan tumpang tindih'192.168.1.0/24' && '192.168.1.128/25'

Berikut peragannya bersama fungsi-fungsi pengurai jaringan yang lain:

-- Operator keanggotaan dan tumpang tindih jaringan
SELECT '192.168.1.5'::inet << '192.168.1.0/24'::cidr AS ip_dalam_jaringan;

SELECT '192.168.1.0/24'::cidr >> '192.168.1.5'::inet AS jaringan_berisi_ip;

SELECT '192.168.1.0/24'::cidr && '192.168.1.128/25'::cidr AS jaringan_bertumpang;

SELECT '192.168.1.0/24'::cidr && '192.168.2.0/24'::cidr AS jaringan_terpisah;

-- Fungsi pengurai alamat dalam satu baris
SELECT family('192.168.1.5/24'::inet)   AS versi,
       host('192.168.1.5/24'::inet)     AS hostnya,
       masklen('192.168.1.5/24'::inet)  AS prefix,
       network('192.168.1.5/24'::inet)  AS jaringan,
       netmask('192.168.1.0/24'::cidr)  AS subnet,
       broadcast('192.168.1.0/24'::cidr) AS alamat_broadcast;

Output yang Dihasilkan:

text
 ip_dalam_jaringan 
-------------------
 t
(1 row)

 jaringan_berisi_ip 
--------------------
 t
(1 row)

 jaringan_bertumpang 
---------------------
 t
(1 row)

 jaringan_terpisah 
-------------------
 f
(1 row)

 versi |   hostnya   | prefix |    jaringan    |    subnet     | alamat_broadcast 
-------+-------------+--------+----------------+---------------+------------------
     4 | 192.168.1.5 |     24 | 192.168.1.0/24 | 255.255.255.0 | 192.168.1.255/24
(1 row)

Query terakhir memperlihatkan perangkat lengkap pengurai alamat: family() untuk versi IP, host() untuk alamat host, masklen() untuk panjang prefix (jumlah bit 1 di netmask), network() untuk jaringan tempat host berada, netmask() untuk bentuk titik-desimal subnet, dan broadcast() untuk alamat broadcast jaringan. Seluruh hitungan bit ini dikerjakan server — pekerjaan yang kalau dikerjakan manual di atas kolom text berarti memparsing string dan menghitung bitmask sendiri di setiap query.

Studi Kasus: Allowlist dengan JOIN #

Skenario klasik tipe jaringan: tabel allowlist berisi daftar jaringan terpercaya, dan kita ingin tahu baris log mana saja yang berasal dari jaringan tersebut. Ini pekerjaan satu JOIN:

sql allowlist.sql
CREATE TABLE allowlist (
    nama     text PRIMARY KEY,
    jaringan cidr NOT NULL
);

INSERT INTO allowlist (nama, jaringan) VALUES
    ('Kantor Pusat', '10.0.0.0/8'),
    ('VPN Mitra',    '172.16.5.0/24'),
    ('Rumah Budi',   '203.0.113.7/32');

-- << menolak kesamaan persis; <<= juga menerima IP tunggal /32
SELECT '203.0.113.7'::inet << '203.0.113.7/32'::cidr  AS ketat,
       '203.0.113.7'::inet <<= '203.0.113.7/32'::cidr AS longgar;

-- IP mana saja di log_firewall yang masuk allowlist?
SELECT l.id, l.sumber, l.aksi, a.nama AS cocok_dengan
FROM log_firewall l
JOIN allowlist a ON l.sumber <<= a.jaringan
ORDER BY l.id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 3
 ketat | longgar 
-------+---------
 f     | t
(1 row)

 id |   sumber    | aksi  | cocok_dengan 
----+-------------+-------+--------------
  1 | 10.0.2.15   | IZIN  | Kantor Pusat
  2 | 203.0.113.7 | TOLAK | Rumah Budi
  4 | 172.16.5.9  | IZIN  | VPN Mitra
(3 rows)

Perhatikan perbedaan hasil kolom ketat dan longgar pada query kedua: operator << menolak kesamaan persis — IP 203.0.113.7 tidak dianggap “terkandung di dalam” jaringan /32 yang beranggotakan satu IP itu sendiri. Operator <<= menutup celah tersebut, dan itulah alasan JOIN memakai <<=: entri allowlist /32 seperti Rumah Budi ikut tercocokkan. Hasil JOIN menampilkan tiga baris — dua aksi IZIN yang memang berasal dari jaringan kantor dan VPN, plus satu aksi TOLAK dari IP rumah Budi yang cocok dengan entri /32.

Perhatikan juga bagaimana demo ini memakai tabel log_firewall dari bagian sebelumnya tanpa membuatnya ulang — seluruh file SQL dalam satu chapter berjalan pada database yang sama, sehingga state menumpuk secara alami.

Validasi Otomatis Menolak Input Rusak #

Sama seperti bytea menolak digit hex palsu, tipe jaringan menolak alamat yang tidak sah — dan pesan errornya cukup spesifik untuk memandu perbaikan:

sql ip-invalid.sql
SELECT '999.168.1.5'::inet;

Output yang Dihasilkan:

text
psql:ip-invalid.sql:1: ERROR:  invalid input syntax for type inet: "999.168.1.5"
LINE 1: SELECT '999.168.1.5'::inet;
               ^
SELECT '192.168.1.5/24'::cidr;

Output yang Dihasilkan:

text
psql:cidr-invalid.sql:1: ERROR:  invalid cidr value: "192.168.1.5/24"
LINE 1: SELECT '192.168.1.5/24'::cidr;
               ^
DETAIL:  Value has bits set to right of mask.

Error pertama terjadi karena 999 melebihi nilai maksimum satu oktet IPv4 (255). Error kedua adalah aturan khas cidr: nilai 192.168.1.5/24 memiliki bit host yang tidak nol di sisi kanan netmask /24, dan cidr menolaknya mentah-mentah. Kalau maksud Anda memang menyimpan host dengan netmask, tipe yang tepat adalah inet; kalau menyimpan jaringan, nol-kan bagian hostnya seperti 192.168.1.0/24.

Kapan Memakai Tipe Jaringan #

KebutuhanTipe yang dipakaiAlasan
Log akses/keamanan per IPinetSatu host per kejadian, mendukung IPv6 dan fungsi family()
Allowlist/blocklist jaringancidrEntri berupa subnet utuh, dari /8 sampai /32
Analisis “IP ini subnet mana?”inet + cidrOperator <</<<= menggantikan parsing manual
Data perangkat jaringanmacaddr / macaddr8Identifikasi NIC, normalisasi format otomatis

Untuk kebutuhan di luar sana — misalnya mencari lokasi geografis dari IP — tipe ini tidak menyediakannya; itu pekerjaan tabel data geolokasi eksternal yang di-JOIN biasa.

Alamat jaringan memetakan data ke posisi di internet. Bab berikutnya menggeser fokus ke ruang fisik: tipe-tipe geometris point, line, box, path, polygon, dan circle yang menyimpan titik koordinat sampai bentuk dua dimensi lengkap dengan operator jarak dan kontainmen — topik chapter Geometric Types.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

allowlist.sql

Unduh berkas
sql
CREATE TABLE allowlist (
    nama     text PRIMARY KEY,
    jaringan cidr NOT NULL
);

INSERT INTO allowlist (nama, jaringan) VALUES
    ('Kantor Pusat', '10.0.0.0/8'),
    ('VPN Mitra',    '172.16.5.0/24'),
    ('Rumah Budi',   '203.0.113.7/32');

-- << menolak kesamaan persis; <<= juga menerima IP tunggal /32
SELECT '203.0.113.7'::inet << '203.0.113.7/32'::cidr  AS ketat,
       '203.0.113.7'::inet <<= '203.0.113.7/32'::cidr AS longgar;

-- IP mana saja di log_firewall yang masuk allowlist?
SELECT l.id, l.sumber, l.aksi, a.nama AS cocok_dengan
FROM log_firewall l
JOIN allowlist a ON l.sumber <<= a.jaringan
ORDER BY l.id;

cidr-invalid.sql

Unduh berkas
sql
SELECT '192.168.1.5/24'::cidr;

ip-invalid.sql

Unduh berkas
sql
SELECT '999.168.1.5'::inet;

operator-jaringan.sql

Unduh berkas
sql
-- Operator keanggotaan dan tumpang tindih jaringan
SELECT '192.168.1.5'::inet << '192.168.1.0/24'::cidr AS ip_dalam_jaringan;

SELECT '192.168.1.0/24'::cidr >> '192.168.1.5'::inet AS jaringan_berisi_ip;

SELECT '192.168.1.0/24'::cidr && '192.168.1.128/25'::cidr AS jaringan_bertumpang;

SELECT '192.168.1.0/24'::cidr && '192.168.2.0/24'::cidr AS jaringan_terpisah;

-- Fungsi pengurai alamat dalam satu baris
SELECT family('192.168.1.5/24'::inet)   AS versi,
       host('192.168.1.5/24'::inet)     AS hostnya,
       masklen('192.168.1.5/24'::inet)  AS prefix,
       network('192.168.1.5/24'::inet)  AS jaringan,
       netmask('192.168.1.0/24'::cidr)  AS subnet,
       broadcast('192.168.1.0/24'::cidr) AS alamat_broadcast;

tabel-log.sql

Unduh berkas
sql
CREATE TABLE log_firewall (
    id     integer PRIMARY KEY,
    waktu  timestamp NOT NULL,
    sumber inet NOT NULL,
    aksi   text NOT NULL
);

INSERT INTO log_firewall (id, waktu, sumber, aksi) VALUES
    (1, '2026-03-01 09:15:00', '10.0.2.15',    'IZIN'),
    (2, '2026-03-01 09:16:30', '203.0.113.7',  'TOLAK'),
    (3, '2026-03-01 09:17:00', '192.168.1.23', 'IZIN'),
    (4, '2026-03-01 09:18:45', '172.16.5.9',   'IZIN'),
    (5, '2026-03-01 09:20:10', '203.0.113.99', 'TOLAK');

SELECT id, sumber,
       host(sumber)   AS ip_murni,
       family(sumber) AS versi_ip,
       aksi
FROM log_firewall
ORDER BY id;

tipe-dasar.sql

Unduh berkas
sql
-- inet menyimpan satu host; netmask opsional setelah garis miring
SELECT '192.168.1.5'::inet AS host_tanpa_netmask;

SELECT '192.168.1.5/24'::inet AS host_dengan_netmask;

-- inet juga menerima IPv6
SELECT '2001:db8::1'::inet AS ipv6;

-- cidr menyimpan jaringan: bagian host wajib nol
SELECT '192.168.1.0/24'::cidr AS jaringan_v4;

SELECT '2001:db8::/32'::cidr AS jaringan_v6;

-- macaddr menyimpan alamat MAC 6 byte, macaddr8 untuk EUI-64 (8 byte)
SELECT '08:00:2b:01:02:03'::macaddr AS mac_enam_byte;

SELECT '08:00:2b:01:02:03:04:05'::macaddr8 AS mac_delapan_byte;