Appearance
PostgreSQL 网络地址函数和操作符
概述
PostgreSQL 提供了强大的网络地址处理能力,支持 IPv4、IPv6 地址以及 MAC 地址的存储和操作。本文详细介绍网络地址类型的操作符和函数,帮助您在实际项目中高效处理网络相关数据。
INFO
网络地址类型概览
PostgreSQL 支持以下网络地址类型:
inet
:存储 IPv4 或 IPv6 主机地址,可选子网掩码cidr
:存储 IPv4 或 IPv6 网络地址macaddr
:存储 MAC 地址(6 字节)macaddr8
:存储 MAC 地址(8 字节,EUI-64 格式)
IP 地址操作符
子网包含性操作符
在网络管理、访问控制和安全策略中,判断 IP 地址是否属于特定子网是常见需求。PostgreSQL 提供了一套完整的包含性操作符。
严格包含操作符 <<
和 >>
业务场景:网络访问控制
假设您在设计一个企业内网访问控制系统,需要判断客户端 IP 是否属于特定的办公区域网段。
sql
-- 检查单个IP是否属于子网
SELECT
inet '192.168.1.5' << inet '192.168.1/24' as "IP在子网内",
inet '192.168.0.5' << inet '192.168.1/24' as "IP不在子网内",
inet '192.168.1/24' << inet '192.168.1/24' as "相同网段";
-- 结果:
-- IP在子网内 | IP不在子网内 | 相同网段
-- t | f | f
sql
-- 企业网络访问控制表
CREATE TABLE network_access_rules (
rule_id SERIAL PRIMARY KEY,
rule_name VARCHAR(100),
allowed_network CIDR,
access_level VARCHAR(20)
);
INSERT INTO network_access_rules VALUES
(1, '总部办公网', '192.168.1.0/24', '完全访问'),
(2, '研发部门', '192.168.10.0/24', '研发访问'),
(3, '访客网络', '192.168.100.0/24', '受限访问');
-- 检查客户端访问权限
SELECT
rule_name,
access_level,
'192.168.1.25'::inet << allowed_network as "访问允许"
FROM network_access_rules
WHERE '192.168.1.25'::inet << allowed_network;
-- 结果:
-- rule_name | access_level | 访问允许
-- 总部办公网 | 完全访问 | t
分析过程:
<<
操作符检查左侧地址是否严格包含在右侧网络中- 严格包含意味着不能相等,必须是真子集关系
- 在访问控制场景中,这有助于区分具体主机和网段本身
包含或等于操作符 <<=
和 >>=
sql
-- 网络层级管理示例
WITH network_hierarchy AS (
SELECT * FROM (VALUES
('企业总网', '192.168.0.0/16'::cidr),
('办公区A', '192.168.1.0/24'::cidr),
('办公区B', '192.168.2.0/24'::cidr),
('服务器区', '192.168.10.0/24'::cidr)
) AS t(name, network)
)
SELECT
a.name as "子网",
b.name as "父网",
a.network <<= b.network as "包含关系"
FROM network_hierarchy a, network_hierarchy b
WHERE a.name != b.name AND a.network <<= b.network;
-- 结果:
-- 子网 | 父网 | 包含关系
-- 办公区A | 企业总网 | t
-- 办公区B | 企业总网 | t
-- 服务器区 | 企业总网 | t
使用 `<<=` 和 `>>=` 操作符可以建立网络层级关系,这在网络拓扑管理和路由规划中非常有用。
重叠检测操作符 &&
sql
-- 网络冲突检测
CREATE TABLE planned_networks (
project_id INT,
project_name VARCHAR(50),
network_range CIDR
);
INSERT INTO planned_networks VALUES
(1, '新办公楼A', '192.168.50.0/24'),
(2, '新办公楼B', '192.168.51.0/24'),
(3, '扩展网段', '192.168.50.128/25'); -- 与办公楼A有重叠
-- 检测网络规划冲突
SELECT
a.project_name as "项目A",
b.project_name as "项目B",
a.network_range,
b.network_range,
a.network_range && b.network_range as "存在重叠"
FROM planned_networks a, planned_networks b
WHERE a.project_id < b.project_id
AND a.network_range && b.network_range;
-- 结果:
-- 项目A | 项目B | network_range | network_range | 存在重叠
-- 新办公楼A | 扩展网段 | 192.168.50.0/24| 192.168.50.128/25| t
按位运算操作符
按位运算在网络地址计算、子网划分和地址转换中发挥重要作用。
按位非运算 ~
sql
-- 计算网络地址的反码(用于某些网络计算)
SELECT
'192.168.1.6'::inet as "原地址",
~ '192.168.1.6'::inet as "按位非",
host(~ '192.168.1.6'::inet) as "反码主机部分";
-- 结果:
-- 原地址 | 按位非 | 反码主机部分
-- 192.168.1.6 | 63.87.254.249 | 63.87.254.249
按位与运算 &
- 提取网络部分
sql
-- 从IP地址中提取特定部分
SELECT
'192.168.1.157'::inet as "原始IP",
'192.168.1.157'::inet & '255.255.255.0'::inet as "网络部分",
'192.168.1.157'::inet & '0.0.0.255'::inet as "主机部分";
-- 结果:
-- 原始IP | 网络部分 | 主机部分
-- 192.168.1.157| 192.168.1.0 | 0.0.0.157
实际应用:子网分析工具
sql
-- 创建IP分析函数
CREATE OR REPLACE FUNCTION analyze_ip(ip_addr inet, subnet_mask inet)
RETURNS TABLE(
original_ip inet,
network_part inet,
host_part inet,
subnet_class text
) AS $$
BEGIN
RETURN QUERY
SELECT
ip_addr,
ip_addr & subnet_mask,
ip_addr & (~ subnet_mask),
CASE
WHEN family(ip_addr) = 4 THEN
CASE
WHEN (ip_addr & '255.0.0.0'::inet) = '10.0.0.0'::inet THEN 'A类私有'
WHEN (ip_addr & '255.240.0.0'::inet) = '172.16.0.0'::inet THEN 'B类私有'
WHEN (ip_addr & '255.255.0.0'::inet) = '192.168.0.0'::inet THEN 'C类私有'
ELSE '公网地址'
END
ELSE 'IPv6地址'
END;
END;
$$ LANGUAGE plpgsql;
-- 使用示例
SELECT * FROM analyze_ip('192.168.1.100'::inet, '255.255.255.0'::inet);
-- 结果:
-- original_ip | network_part | host_part | subnet_class
-- 192.168.1.100 | 192.168.1.0 | 0.0.0.100 | C类私有
按位或运算 |
- 地址合并
sql
-- 生成广播地址或合并地址段
SELECT
'192.168.1.0'::inet as "网络地址",
'0.0.0.255'::inet as "主机掩码",
'192.168.1.0'::inet | '0.0.0.255'::inet as "广播地址";
-- 结果:
-- 网络地址 | 主机掩码 | 广播地址
-- 192.168.1.0 | 0.0.0.255 | 192.168.1.255
算术运算操作符
地址偏移运算 +
和 -
在 DHCP 地址分配、负载均衡器配置等场景中,需要对 IP 地址进行算术运算。
sql
-- DHCP地址池管理示例
CREATE TABLE dhcp_pools (
pool_id SERIAL PRIMARY KEY,
pool_name VARCHAR(50),
network_base INET,
pool_start INTEGER,
pool_end INTEGER
);
INSERT INTO dhcp_pools VALUES
(1, '办公区DHCP池', '192.168.1.0', 10, 200);
-- 生成可用IP地址列表
WITH RECURSIVE ip_range AS (
SELECT
pool_name,
network_base + pool_start as current_ip,
pool_start as current_offset,
pool_end
FROM dhcp_pools WHERE pool_id = 1
UNION ALL
SELECT
pool_name,
network_base + (current_offset + 1),
current_offset + 1,
pool_end
FROM ip_range, dhcp_pools
WHERE dhcp_pools.pool_id = 1
AND current_offset < pool_end
AND current_offset < pool_start + 5 -- 限制显示前5个
)
SELECT pool_name, current_ip, current_offset as "偏移量"
FROM ip_range;
-- 结果:
-- pool_name | current_ip | 偏移量
-- 办公区DHCP池 | 192.168.1.10 | 10
-- 办公区DHCP池 | 192.168.1.11 | 11
-- 办公区DHCP池 | 192.168.1.12 | 12
-- 办公区DHCP池 | 192.168.1.13 | 13
-- 办公区DHCP池 | 192.168.1.14 | 14
-- 办公区DHCP池 | 192.168.1.15 | 15
地址差值计算
sql
-- 计算IP地址范围大小
SELECT
'192.168.1.100'::inet as "起始地址",
'192.168.1.200'::inet as "结束地址",
'192.168.1.200'::inet - '192.168.1.100'::inet as "地址数量",
('192.168.1.200'::inet - '192.168.1.100'::inet) + 1 as "包含边界的总数";
-- 结果:
-- 起始地址 | 结束地址 | 地址数量 | 包含边界的总数
-- 192.168.1.100 | 192.168.1.200 | 100 | 101
IP 地址函数
地址格式化函数
abbrev()
- 创建缩写显示
sql
-- 比较不同格式的地址显示
SELECT
'10.1.0.0/32'::inet as "原始格式",
abbrev('10.1.0.0/32'::inet) as "inet缩写",
'10.1.0.0/16'::cidr as "CIDR原始",
abbrev('10.1.0.0/16'::cidr) as "CIDR缩写";
-- 结果:
-- 原始格式 | inet缩写 | CIDR原始 | CIDR缩写
-- 10.1.0.0/32 | 10.1.0.0 | 10.1.0.0/16 | 10.1/16
host()
和 text()
- 地址文本转换
sql
-- 从完整地址信息中提取主机部分
CREATE TABLE server_inventory (
server_name VARCHAR(50),
ip_address INET
);
INSERT INTO server_inventory VALUES
('Web服务器1', '192.168.1.10/24'),
('数据库服务器', '192.168.1.20/24'),
('文件服务器', '192.168.1.30/24');
SELECT
server_name,
ip_address as "完整地址",
host(ip_address) as "主机IP",
text(ip_address) as "完整文本格式"
FROM server_inventory;
-- 结果:
-- server_name | 完整地址 | 主机IP | 完整文本格式
-- Web服务器1 | 192.168.1.10/24 | 192.168.1.10 | 192.168.1.10/24
-- 数据库服务器 | 192.168.1.20/24 | 192.168.1.20 | 192.168.1.20/24
-- 文件服务器 | 192.168.1.30/24 | 192.168.1.30 | 192.168.1.30/24
网络计算函数
broadcast()
- 计算广播地址
sql
-- 网络广播地址计算(用于网络唤醒等功能)
SELECT
'192.168.1.5/24'::inet as "任意主机",
broadcast('192.168.1.5/24'::inet) as "广播地址",
'10.0.0.1/8'::inet as "大型网络",
broadcast('10.0.0.1/8'::inet) as "大型网络广播";
-- 结果:
-- 任意主机 | 广播地址 | 大型网络 | 大型网络广播
-- 192.168.1.5/24 | 192.168.1.255/24 | 10.0.0.1/8 | 10.255.255.255/8
netmask()
和 hostmask()
- 掩码计算
sql
-- 子网掩码和主机掩码计算
SELECT
'192.168.23.20/30'::inet as "原地址",
netmask('192.168.23.20/30'::inet) as "子网掩码",
hostmask('192.168.23.20/30'::inet) as "主机掩码",
masklen('192.168.23.20/30'::inet) as "掩码长度";
-- 结果:
-- 原地址 | 子网掩码 | 主机掩码 | 掩码长度
-- 192.168.23.20/30 | 255.255.255.252 | 0.0.0.3 | 30
实际应用:子网规划计算器
sql
CREATE OR REPLACE FUNCTION subnet_calculator(
base_network INET,
required_hosts INTEGER
)
RETURNS TABLE(
network_address CIDR,
subnet_mask INET,
host_mask INET,
broadcast_addr INET,
first_usable INET,
last_usable INET,
total_hosts BIGINT,
usable_hosts BIGINT
) AS $$
DECLARE
required_bits INTEGER;
subnet_bits INTEGER;
BEGIN
-- 计算需要的主机位数
required_bits := CEIL(LOG(2, required_hosts + 2)); -- +2 for network and broadcast
subnet_bits := 32 - required_bits;
-- 调整网络地址的掩码
base_network := set_masklen(base_network, subnet_bits);
RETURN QUERY
SELECT
network(base_network),
netmask(base_network),
hostmask(base_network),
broadcast(base_network),
network(base_network) + 1,
broadcast(base_network) - 1,
2::BIGINT ^ required_bits,
(2::BIGINT ^ required_bits) - 2;
END;
$$ LANGUAGE plpgsql;
-- 使用示例:为100台主机规划子网
SELECT * FROM subnet_calculator('192.168.1.0/24'::inet, 100);
-- 结果:
-- network_address | subnet_mask | host_mask | broadcast_addr | first_usable | last_usable | total_hosts | usable_hosts
-- 192.168.1.0/25 | 255.255.255.128 | 0.0.0.127 | 192.168.1.127/25 | 192.168.1.1 | 192.168.1.126 | 128 | 126
network()
和 set_masklen()
- 网络地址操作
sql
-- 网络地址标准化和掩码调整
SELECT
'192.168.1.5/24'::inet as "主机地址",
network('192.168.1.5/24'::inet) as "网络地址",
set_masklen('192.168.1.5/24'::inet, 16) as "调整掩码";
-- 对于CIDR类型
SELECT
'192.168.1.0/24'::cidr as "原网络",
set_masklen('192.168.1.0/24'::cidr, 16) as "扩大网络";
-- 结果:
-- 主机地址 | 网络地址 | 调整掩码
-- 192.168.1.5/24 | 192.168.1.0/24 | 192.168.1.5/16
-- 原网络 | 扩大网络
-- 192.168.1.0/24 | 192.168.0.0/16
inet_merge()
- 网络合并
sql
-- 计算包含多个网络的最小网络(网络聚合)
WITH branch_networks AS (
SELECT * FROM (VALUES
('分公司A', '192.168.1.0/24'::inet),
('分公司B', '192.168.2.0/24'::inet),
('分公司C', '192.168.4.0/24'::inet),
('分公司D', '192.168.5.0/24'::inet)
) AS t(branch, network)
)
SELECT
string_agg(branch, ', ') as "分公司列表",
inet_merge(
'192.168.1.0/24'::inet,
'192.168.5.0/24'::inet
) as "聚合网络";
-- 结果:
-- 分公司列表 | 聚合网络
-- 分公司A, 分公司B, 分公司C, 分公司D | 192.168.0.0/21
family()
和 inet_same_family()
- 地址族检测
sql
-- 混合IPv4/IPv6环境下的地址处理
CREATE TABLE network_devices (
device_id SERIAL PRIMARY KEY,
device_name VARCHAR(50),
ipv4_address INET,
ipv6_address INET
);
INSERT INTO network_devices VALUES
(1, '路由器A', '192.168.1.1/24', '2001:db8::1/64'),
(2, '交换机B', '192.168.1.2/24', '2001:db8::2/64'),
(3, '服务器C', '192.168.1.10/24', NULL);
SELECT
device_name,
ipv4_address,
family(ipv4_address) as "IPv4族",
ipv6_address,
CASE
WHEN ipv6_address IS NOT NULL THEN family(ipv6_address)
ELSE NULL
END as "IPv6族",
CASE
WHEN ipv6_address IS NOT NULL THEN
inet_same_family(ipv4_address, ipv6_address)
ELSE NULL
END as "同族检测"
FROM network_devices;
-- 结果:
-- device_name | ipv4_address | IPv4族 | ipv6_address | IPv6族 | 同族检测
-- 路由器A | 192.168.1.1/24 | 4 | 2001:db8::1/64 | 6 | f
-- 交换机B | 192.168.1.2/24 | 4 | 2001:db8::2/64 | 6 | f
-- 服务器C | 192.168.1.10/24 | 4 | NULL | NULL | NULL
MAC 地址函数
trunc()
- MAC 地址截断
MAC 地址的前 3 字节(24 位)通常标识制造商(OUI - Organizationally Unique Identifier)。
sql
-- MAC地址制造商标识提取
CREATE TABLE network_cards (
card_id SERIAL PRIMARY KEY,
card_model VARCHAR(50),
mac_address MACADDR
);
INSERT INTO network_cards VALUES
(1, 'Intel 网卡', '12:34:56:78:90:ab'),
(2, 'Broadcom 网卡', 'aa:bb:cc:dd:ee:ff'),
(3, 'Realtek 网卡', '00:11:22:33:44:55');
SELECT
card_model,
mac_address as "完整MAC",
trunc(mac_address) as "制造商OUI"
FROM network_cards;
-- 结果:
-- card_model | 完整MAC | 制造商OUI
-- Intel 网卡 | 12:34:56:78:90:ab | 12:34:56:00:00:00
-- Broadcom 网卡 | aa:bb:cc:dd:ee:ff | aa:bb:cc:00:00:00
-- Realtek 网卡 | 00:11:22:33:44:55 | 00:11:22:00:00:00
macaddr8_set7bit()
- EUI-64 转换
在 IPv6 环境中,经常需要将 MAC 地址转换为 EUI-64 格式以生成链路本地地址。
sql
-- IPv6 链路本地地址生成示例
WITH mac_addresses AS (
SELECT '00:1a:2b:3c:4d:5e'::macaddr8 as original_mac
)
SELECT
original_mac as "原始MAC",
macaddr8_set7bit(original_mac) as "EUI-64格式",
-- 生成IPv6链路本地地址前缀(实际应用中需要额外处理)
'fe80::' ||
replace(
replace(macaddr8_set7bit(original_mac)::text, ':', ''),
'fffe', ':fffe:'
) as "IPv6链路本地地址示例"
FROM mac_addresses;
-- 结果:
-- 原始MAC | EUI-64格式 | IPv6链路本地地址示例
-- 00:1a:2b:3c:4d:5e | 02:1a:2b:ff:fe:3c:4d:5e | fe80::021a:2b:fffe:3c4d5e
综合应用案例
案例 1:网络资产管理系统
sql
-- 创建完整的网络资产管理表结构
CREATE TABLE network_segments (
segment_id SERIAL PRIMARY KEY,
segment_name VARCHAR(100),
network_range CIDR,
vlan_id INTEGER,
description TEXT
);
CREATE TABLE network_assets (
asset_id SERIAL PRIMARY KEY,
asset_name VARCHAR(100),
asset_type VARCHAR(50),
ip_address INET,
mac_address MACADDR,
segment_id INTEGER REFERENCES network_segments(segment_id)
);
-- 插入示例数据
INSERT INTO network_segments VALUES
(1, '管理网段', '192.168.1.0/24', 10, '网络设备管理'),
(2, '办公网段', '192.168.10.0/24', 20, '员工办公设备'),
(3, '服务器网段', '192.168.100.0/24', 30, '服务器和应用');
INSERT INTO network_assets VALUES
(1, '核心交换机', '交换机', '192.168.1.1/24', '00:11:22:33:44:55', 1),
(2, 'Web服务器', '服务器', '192.168.100.10/24', 'aa:bb:cc:dd:ee:ff', 3),
(3, '员工PC-001', '工作站', '192.168.10.100/24', '12:34:56:78:90:ab', 2);
-- 网络资产分析查询
SELECT
ns.segment_name,
ns.network_range,
na.asset_name,
na.asset_type,
host(na.ip_address) as "设备IP",
na.mac_address,
trunc(na.mac_address) as "制造商OUI",
-- 检查IP是否在正确的网段
na.ip_address << ns.network_range as "IP匹配网段",
-- 计算该设备在网段中的位置
na.ip_address - network(ns.network_range) as "网段内偏移"
FROM network_assets na
JOIN network_segments ns ON na.segment_id = ns.segment_id
ORDER BY ns.segment_id, na.asset_id;
案例 2:网络安全监控
sql
-- 创建网络访问日志表
CREATE TABLE access_logs (
log_id SERIAL PRIMARY KEY,
timestamp TIMESTAMP DEFAULT NOW(),
source_ip INET,
destination_ip INET,
action VARCHAR(20),
protocol VARCHAR(10)
);
-- 创建安全策略表
CREATE TABLE security_policies (
policy_id SERIAL PRIMARY KEY,
policy_name VARCHAR(100),
source_network CIDR,
destination_network CIDR,
action VARCHAR(20) -- ALLOW, DENY, LOG
);
INSERT INTO security_policies VALUES
(1, '内网互访', '192.168.0.0/16', '192.168.0.0/16', 'ALLOW'),
(2, '拒绝外网访问内网', '0.0.0.0/0', '192.168.0.0/16', 'DENY'),
(3, '允许访问DMZ', '192.168.0.0/16', '10.0.1.0/24', 'ALLOW');
-- 模拟访问日志
INSERT INTO access_logs (source_ip, destination_ip, action, protocol) VALUES
('192.168.1.100', '192.168.1.200', 'ALLOW', 'TCP'),
('203.0.113.100', '192.168.1.50', 'DENY', 'TCP'),
('192.168.10.50', '10.0.1.10', 'ALLOW', 'HTTP');
-- 安全分析:检查违规访问
WITH policy_violations AS (
SELECT
al.*,
sp.policy_name,
sp.action as expected_action,
-- 检查源IP是否匹配策略
al.source_ip << sp.source_network as source_match,
-- 检查目标IP是否匹配策略
al.destination_ip << sp.destination_network as dest_match
FROM access_logs al
CROSS JOIN security_policies sp
WHERE al.source_ip << sp.source_network
AND al.destination_ip << sp.destination_network
)
SELECT
timestamp,
host(source_ip) as "源IP",
host(destination_ip) as "目标IP",
policy_name as "匹配策略",
expected_action as "期望动作",
action as "实际动作",
CASE
WHEN expected_action != action THEN '违规'
ELSE '正常'
END as "状态"
FROM policy_violations
ORDER BY timestamp DESC;
性能优化建议
WARNING
性能注意事项
- 索引使用:在 INET/CIDR 列上创建 GiST 索引以优化包含性查询
- 数据类型选择:根据具体需求选择 INET 或 CIDR
- 查询优化:避免在大表上进行全表扫描的网络运算
sql
-- 创建网络地址索引
CREATE INDEX idx_network_assets_ip ON network_assets USING GIST (ip_address inet_ops);
CREATE INDEX idx_network_segments_range ON network_segments USING GIST (network_range inet_ops);
-- 优化的查询示例
EXPLAIN (ANALYZE, BUFFERS)
SELECT asset_name
FROM network_assets
WHERE ip_address << '192.168.1.0/24'::cidr;
总结
PostgreSQL 的网络地址函数和操作符为网络应用开发提供了强大的工具集。通过合理使用这些功能,可以:
- 简化网络逻辑:直接在数据库层面处理复杂的网络计算
- 提高性能:利用专门的索引和优化算法
- 增强安全性:实现精确的网络访问控制
- 便于维护:将网络逻辑集中在数据库中,减少应用层复杂性
在实际项目中,建议结合具体业务需求,选择合适的数据类型和操作符,并注意性能优化和索引策略的应用。