🌐

PostgreSQL Network Address Functions

Complete reference for PostgreSQL network address functions covering INET, CIDR, and MACADDR types. Includes address validation, subnet containment tests, network math, broadcast address extraction, and host range functions. Updated for PostgreSQL 16.

9 functions

What are PostgreSQL Network Address Functions?

PostgreSQL network address functions are built-in functions for working with IPv4, IPv6, and MAC address data. The INET type stores individual host addresses with optional netmask, while CIDR stores network addresses with a required netmask. These functions support subnet containment tests, address arithmetic, broadcast address computation, and host extraction, making them ideal for network inventory and firewall rule management.

host

PG 7.4+text

Returns the host address from an inet value as text, without the subnet mask.

DeveloperDBASecurity

Signature

host ( inet ) → text

Parameters

ParameterTypeDescription
addressinet or cidrNetwork address

Examples

sql
SELECT host('192.168.1.1/24'::inet);
192.168.1.1
sql
SELECT host('::1/128'::inet);
::1
sql
SELECT host(client_ip) AS plain_ip FROM connections WHERE host(client_ip) = '10.0.0.1';
10.0.0.1
sql
SELECT host(ip_address), masklen(ip_address) FROM servers ORDER BY ip_address;
host and prefix length for each server
Anti-PatternUsing host() for display when abbrev() is cleaner

`host()` always strips the prefix and returns a bare IP string, but for user-facing display `abbrev()` is more informative — it drops the /32 suffix for single hosts while keeping the prefix for networks. Defaulting to `host()` everywhere discards CIDR context that security reviewers often need.

✓ Instead: Use `abbrev(ip_col)` for display: it renders single-host /32 addresses as plain IPs and keeps meaningful prefixes on network addresses, so the output is compact without throwing away subnet information.

`host(inet_col)` extracts the IP string without the prefix length. Use this when you need to compare or display the IP address as a plain string.

example
SELECT host(ip_address) AS ip, masklen(ip_address) AS prefix FROM clients;
IP and prefix length as separate values

masklen

PG 7.4+integer

Returns the subnet mask length (prefix length) of a network address.

DeveloperDBASecurity

Signature

masklen ( inet ) → integer

Parameters

ParameterTypeDescription
addressinet or cidrNetwork address

Examples

sql
SELECT masklen('192.168.1.0/24'::inet);
24
sql
SELECT masklen('10.0.0.0/8'::cidr);
8
sql
SELECT cidr_block, masklen(cidr_block) AS prefix FROM ip_allocations WHERE masklen(cidr_block) BETWEEN 24 AND 32;
All host and small-subnet allocations
sql
SELECT masklen('ffff:ffff:ffff:ffff::/64'::inet);
64
Anti-PatternStoring prefix length as a separate integer column instead of using inet/cidr

Some schemas store an IP address as `TEXT` and prefix length as a separate `INTEGER` column, then use `masklen()` to reconstruct logic at query time. This splits what is naturally a single value, requires extra joins or concatenation to rebuild a usable CIDR, and prevents the optimizer from using GiST indexes on containment queries.

✓ Instead: Store the address and its prefix together as `inet` or `cidr`. Use `masklen(col)` only to read or filter the prefix — not as a substitute for proper type design.

Use `masklen(cidr_col) = 24` to find all /24 networks, or `masklen < 24` to find supernets. Useful when categorizing IP ranges in access control tables.

example
SELECT network, masklen(network) FROM ip_ranges ORDER BY masklen(network);
Networks sorted from broadest to narrowest

network

PG 7.4+cidr

Returns the network address (with host bits zeroed) of an inet value as a cidr.

DeveloperDBASecurity

Signature

network ( inet ) → cidr

Parameters

ParameterTypeDescription
addressinetIP address with prefix length

Examples

sql
SELECT network('192.168.1.100/24'::inet);
192.168.1.0/24
sql
SELECT network('10.20.30.40/16'::inet);
10.20.0.0/16
sql
SELECT network(ip_address) AS subnet, count(*) FROM hosts GROUP BY 1 ORDER BY 1;
Host count per subnet
sql
SELECT network('2001:db8:85a3::8a2e:370:7334/48'::inet);
2001:db8:85a3::/48
Anti-PatternNot using << / <<= for subnet containment queries

A common mistake is extracting the network address with `network(ip_col)` and then doing string or equality comparisons to check subnet membership, e.g., `WHERE network(ip_address) = '10.0.0.0/8'`. This only matches addresses that are exactly in that network at exactly that prefix length, missing all longer-prefix subnets contained within it.

✓ Instead: Use the containment operator directly: `WHERE ip_address << '10.0.0.0/8'::cidr` (strictly contained) or `WHERE ip_address <<= '10.0.0.0/8'::cidr` (contained or equal). These operators work with GiST indexes and correctly handle nested subnets.

Use `network(ip_col)` in a GROUP BY to aggregate by subnet. All IPs in the same /24 will map to the same CIDR, making it easy to count devices per network.

example
SELECT network(ip_address) AS subnet, count(*) AS devices FROM servers GROUP BY 1 ORDER BY 1;
Device count per subnet

broadcast

PG 7.4+inet

Returns the broadcast address of the network indicated by the inet value.

DeveloperDBASecurity

Signature

broadcast ( inet ) → inet

Parameters

ParameterTypeDescription
addressinetNetwork address

Examples

sql
SELECT broadcast('192.168.1.0/24'::inet);
192.168.1.255/24
sql
SELECT broadcast('10.0.0.0/8'::inet);
10.255.255.255/8
sql
SELECT network(ip_address) AS start_addr, broadcast(ip_address) AS end_addr FROM subnets;
Full address range for each subnet
sql
SELECT broadcast('172.16.0.0/12'::inet);
172.31.255.255/12
Anti-PatternManually computing the broadcast address with bitwise arithmetic on text

Some developers store IPs as text and compute broadcast addresses with string manipulation or by casting to integers and using bitwise OR. This is error-prone for IPv6, breaks on unexpected prefix lengths, and cannot leverage PostgreSQL's native operator optimizations.

✓ Instead: Store addresses as `inet` or `cidr` and call `broadcast(col)` directly. For range queries, prefer the `<<` and `<<=` containment operators over computing and comparing broadcast boundaries manually.

Use `network(addr)` for the start and `broadcast(addr)` for the end of a subnet's address range. Combine with `<<` containment operator to check if an IP falls within a subnet.

example
SELECT * FROM servers WHERE ip_address << '192.168.0.0/16'::cidr;
All servers in the 192.168.x.x range

inet_same_family

PG 7.4+boolean

Returns true if both inet values belong to the same IP address family (IPv4 or IPv6).

DeveloperDBASecurity

Signature

inet_same_family ( inet, inet ) → boolean

Parameters

ParameterTypeDescription
ainetFirst address
binetSecond address

Examples

sql
SELECT inet_same_family('192.168.1.1'::inet, '10.0.0.1'::inet);
true (both IPv4)
sql
SELECT inet_same_family('::1'::inet, '127.0.0.1'::inet);
false (IPv6 vs IPv4)
sql
SELECT client_ip, range_cidr FROM allowed_ranges WHERE inet_same_family(client_ip, range_cidr) AND client_ip <<= range_cidr;
Only same-family containment matches
sql
SELECT count(*) FILTER (WHERE NOT inet_same_family(src_ip, dst_ip)) AS cross_family_flows FROM traffic_log;
Number of cross-family flows (likely anomalous)
Anti-PatternComparing inet values across address families without a family guard

Applying the `<<` (contained-within) or `=` operators between an IPv4 address and an IPv6 CIDR silently returns false rather than raising an error. In access-control logic this can cause allowlist rules to be silently skipped, creating security gaps that are hard to detect in testing if your test data happens to be single-family.

✓ Instead: Always guard cross-family comparisons: `WHERE inet_same_family(client_ip, allowed_cidr) AND client_ip <<= allowed_cidr`. In dual-stack environments, store separate allowlist rows for IPv4 and IPv6, or use `family(col)` in a partial index to keep them segregated.

Comparing an IPv4 address with an IPv6 CIDR range is meaningless. Use `inet_same_family(ip, range)` as a guard before containment checks in multi-family environments.

example
SELECT * FROM allowed_ranges WHERE inet_same_family(client_ip, range_cidr) AND client_ip << range_cidr;
Correct containment check across dual-stack environments

family

PG 7.4+integer

Returns the address family of an inet value: 4 for IPv4, 6 for IPv6.

DeveloperDBASecurity

Signature

family ( inet ) → integer

Parameters

ParameterTypeDescription
addressinet or cidrNetwork address to check

Examples

sql
SELECT family('192.168.1.1'::inet);
4
sql
SELECT family('::1'::inet);
6
sql
SELECT count(*) FILTER (WHERE family(ip) = 6) AS ipv6_count FROM connections;
Count of IPv6 connections
sql
SELECT family(ip_address), count(*) FROM access_log GROUP BY 1;
4: 12048, 6: 952
Anti-PatternStoring IP version as a separate text or integer column instead of using family()

Some schemas add an explicit `ip_version` column (e.g., `'IPv4'` or `4`) alongside the `inet` column. This duplicates information already encoded in the `inet` type, introduces the risk of the columns drifting out of sync, and adds unnecessary storage and maintenance overhead.

✓ Instead: Derive the IP version on demand with `family(ip_col)`. If you need to index by family, use a partial index: `CREATE INDEX ON connections (client_ip) WHERE family(client_ip) = 6;` rather than a redundant column.

Use `family(ip_col)` in aggregate queries to analyze IPv4 vs IPv6 distribution: `GROUP BY family(ip)` or with `FILTER (WHERE family(ip) = 6)` for IPv6-specific stats.

example
SELECT family(client_ip) AS ip_version, count(*) FROM access_log GROUP BY 1;
4: 12345, 6: 678 (request counts by IP version)

abbrev

PG 7.4+text

Returns an abbreviated display text for the address, removing the prefix length if it's the default (/32 for IPv4, /128 for IPv6).

DeveloperDBASecurity

Signatures

abbrev ( inet ) → text
abbrev ( cidr ) → text

Parameters

ParameterTypeDescription
addressinet or cidrNetwork address to abbreviate

Examples

sql
SELECT abbrev('192.168.1.1/32'::inet);
192.168.1.1
sql
SELECT abbrev('10.0.0.0/8'::cidr);
10/8
sql
SELECT abbrev('::1/128'::inet);
::1
sql
SELECT abbrev(ip_address) AS display_ip FROM audit_log ORDER BY logged_at DESC LIMIT 10;
Clean IP strings without redundant /32 or /128 suffixes
Anti-PatternUsing host() for display when abbrev() is cleaner

`host()` always strips the prefix entirely, producing a bare IP string. For network addresses like `10.0.0.0/8` this throws away the prefix, leaving just `10.0.0.0` — which loses the subnet context. `abbrev()` keeps the prefix for non-default lengths and only drops it when it is trivial (/32, /128).

✓ Instead: Prefer `abbrev(col)` for display output. Reserve `host(col)` for cases where you specifically need a bare IP string with no prefix information, such as DNS lookups or legacy text comparisons.

`abbrev` is the cleanest way to display network addresses for users — it removes the /32 suffix for single hosts and abbreviates CIDR notation (e.g., 10/8 instead of 10.0.0.0/8).

example
SELECT abbrev(ip_address) AS ip FROM users;
Clean IP display without /32 suffix

trunc

PG 7.4+macaddr

Sets the last 3 bytes of a MAC address to zero, giving only the OUI (manufacturer prefix).

DeveloperDBASecurity

Signature

trunc ( macaddr ) → macaddr

Parameters

ParameterTypeDescription
macaddrmacaddrMAC address to truncate

Examples

sql
SELECT trunc('12:34:56:78:90:AB'::macaddr);
12:34:56:00:00:00
sql
SELECT trunc(mac) AS oui, count(*) FROM devices GROUP BY 1;
Device count per manufacturer OUI
sql
SELECT trunc(mac_address) AS oui, count(*) AS cnt FROM network_devices GROUP BY 1 ORDER BY cnt DESC LIMIT 5;
Top 5 manufacturer OUIs by device count
sql
SELECT CASE WHEN trunc(mac_address) = 'b8:27:eb:00:00:00'::macaddr THEN 'Raspberry Pi Foundation' ELSE 'Other' END AS vendor FROM endpoints;
Vendor label for each endpoint
Anti-PatternStoring MAC addresses as text and parsing OUI with string functions

Storing MAC addresses as `TEXT` and then extracting the OUI with `SUBSTRING(mac, 1, 8)` or `SPLIT_PART(mac, ':', 1) || ':' || ...` is fragile: it breaks on different separator styles (colons vs dashes vs no separator), mixed case, and short-form notation. It also prevents using `macaddr` operators for sorting and comparison.

✓ Instead: Store MAC addresses as the `macaddr` type and use `trunc(mac_col)` to extract the OUI. PostgreSQL normalises the format on input, so all comparison and grouping operations are reliable regardless of how the MAC was originally entered.

Use `trunc(mac_address)` to extract the OUI (Organizationally Unique Identifier) and group devices by manufacturer. The OUI database maps these 3-byte prefixes to vendor names.

example
SELECT trunc(mac_address) AS manufacturer_oui, count(*) AS device_count FROM network_devices GROUP BY 1 ORDER BY 2 DESC;
Top manufacturers by device count

inet_merge

PG 7.4+cidr

Returns the smallest network that includes both of the given networks.

DeveloperDBASecurity

Signature

inet_merge ( inet, inet ) → cidr

Parameters

ParameterTypeDescription
ainet or cidrFirst network address
binet or cidrSecond network address

Examples

sql
SELECT inet_merge('192.168.1.0/24', '192.168.2.0/24');
192.168.0.0/22
sql
SELECT inet_merge('10.0.0.1', '10.0.0.254');
10.0.0.0/24
sql
SELECT inet_merge(min(client_ip), max(client_ip)) AS covering_cidr FROM session_ips;
Smallest CIDR covering the full client IP range
sql
SELECT inet_merge('192.168.100.0/24', '192.168.200.0/24');
192.168.0.0/17
Anti-PatternNot indexing inet columns used in subnet containment queries with GiST

Using `inet_merge` and the `<<` / `<<=` operators for subnet lookups without a GiST index causes sequential scans. On tables with millions of IP rows — typical in firewall logs, connection tables, or access-control lists — this can make subnet queries orders of magnitude slower than necessary.

✓ Instead: Create a GiST index on any `inet` or `cidr` column that participates in containment queries: `CREATE INDEX ON firewall_rules USING GIST (allowed_cidr inet_ops);`. The `inet_ops` operator class enables fast `<<`, `<<=`, `>>`, and `>>=` lookups without a full scan.

Use `inet_merge` to find the smallest supernet covering two addresses. This is useful for route summarization and access control rule consolidation.

example
SELECT inet_merge(min(client_ip), max(client_ip)) AS covering_range FROM session_ips;
Smallest CIDR covering all client IPs