easyswoole / ip-access
a simple white/black access manager
Requires
- php: >=8.1
Requires (Dev)
- phpunit/phpunit: ^9.6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
一个支持 IPv4、IPv6、CIDR 网段及端口范围的 PHP IP 访问控制组件。
要求 PHP 8.1 或更高版本。
安装
composer require easyswoole/ip-access
IpAccess:IP 黑白名单
IpAccess 仅根据 IP 地址判断是否允许访问。
<?php use EasySwoole\IpAccess\IpAccess; $access = new IpAccess(); // 添加单个白名单 IP $access->addWhiteIp('127.0.0.1'); // 添加白名单网段 $access->addWhiteIpSegment('172.16.0.0/16'); // 添加单个黑名单 IP $access->addBlackIp('192.168.0.1'); // 添加黑名单网段 $access->addBlackIpSegment('192.168.1.0/24'); // 黑名单网段中的单个白名单例外 $access->addWhiteIp('192.168.1.31'); $access->isAllowAccess('127.0.0.1'); // true $access->isAllowAccess('192.168.0.1'); // false $access->isAllowAccess('192.168.1.31'); // true $access->isAllowAccess('192.168.1.32'); // false $access->isAllowAccess('10.0.0.1'); // true,未命中规则时默认放行
所有 add* 方法都会返回当前对象,因此可以链式调用:
$access ->addBlackIpSegment('10.0.0.0/8') ->addWhiteIp('10.0.0.10');
IpAccess 优先级
判断顺序由高到低如下:
- 单个白名单 IP
- 单个黑名单 IP
- 白名单网段
- 黑名单网段
- 未命中任何规则时默认放行
单个 IP 规则比网段规则更精确,因此优先级更高。例如,可以通过单个白名单 IP 放行黑名单网段中的某个地址;也可以通过单个黑名单 IP,封禁白名单网段中的某个地址。
IpAccess 冲突规则
- 同一个 IP 不能同时存在于单个 IP 白名单和黑名单中。
- 白名单网段和黑名单网段的地址范围不能重叠。比如已有白名单
192.168.1.0/24时,不能添加黑名单192.168.1.128/25。 - 同一类型的规则可以重复添加;相同名单中的网段允许重叠。
- 单个 IP 与相反类型的网段允许重叠,用于设置精确例外。
- 发生冲突时抛出
LogicException,异常信息会包含本次添加的规则和已有的冲突规则。 - IP 或 CIDR 格式非法时抛出
InvalidArgumentException。
IpPortAccess:IP 端口访问规则
IpPortAccess 根据 IP 地址和端口共同判断是否允许访问。端口既可以使用整数,也可以使用仅包含数字的字符串。
<?php use EasySwoole\IpAccess\IpPortAccess; $access = new IpPortAccess(); // 单个 IP + 单端口 $access->addIpAllowPort('127.0.0.1', '80'); $access->addIpBlockPort('192.168.0.1', '443'); // 单个 IP + 端口闭区间(包含起止端口) $access->addIpAllowPort('127.0.0.1', '8000', '9000'); $access->addIpBlockPort('192.168.0.1', '1000', '2000'); // 网段 + 单端口 $access->addIpSegmentAllowPort('172.16.0.0/16', '80'); $access->addIpSegmentBlockPort('192.168.1.0/24', '443'); // 网段 + 端口闭区间 $access->addIpSegmentAllowPort('172.17.0.0/16', '8000', '9000'); $access->addIpSegmentBlockPort('192.168.2.0/24', '1000', '2000'); $access->isAllowAccess('127.0.0.1', '80'); // true $access->isAllowAccess('192.168.0.1', '443'); // false $access->isAllowAccess('10.0.0.1', '80'); // true,未命中规则时默认放行
也可以在被封禁的网段和端口范围中添加精确 IP+端口例外:
$access = new IpPortAccess(); $access ->addIpSegmentBlockPort('192.168.1.0/24', 8000, 9000) ->addIpAllowPort('192.168.1.31', 8080); $access->isAllowAccess('192.168.1.31', 8080); // true,命中精确放行规则 $access->isAllowAccess('192.168.1.31', 8081); // false,回退并命中网段封禁规则 $access->isAllowAccess('192.168.1.32', 8080); // false
IpPortAccess 优先级
判断顺序由高到低如下:
- 单个 IP 的放行端口规则
- 单个 IP 的封禁端口规则
- IP 网段的放行端口规则
- IP 网段的封禁端口规则
- 未命中任何规则时默认放行
只有 IP 和端口同时命中规则,该规则才生效。某个 IP 存在精确规则,并不意味着它的所有端口都会停止匹配网段规则;如果当前端口没有命中精确规则,会继续检查网段规则。
IpPortAccess 冲突规则
- 对于同一个精确 IP,放行和封禁端口范围不能重叠。
- 对于网段规则,只有“IP 地址范围重叠”且“端口范围也重叠”时才构成冲突。
- 网段地址范围重叠但端口范围不重叠时,可以同时添加。
- 相同类型的规则允许重叠或重复添加。
- 精确 IP+端口规则与相反类型的网段+端口规则允许重叠,用于设置精确例外。
- 端口范围是闭区间,端口必须在
1到65535之间,且起始端口不能大于结束端口。 - 发生冲突时抛出
LogicException,异常信息会包含本次添加的规则和已有的冲突规则。 - IP、CIDR 或端口格式非法时抛出
InvalidArgumentException。
例如,以下规则会发生冲突:
$access = new IpPortAccess(); $access->addIpSegmentBlockPort('10.0.0.0/8', 80, 100); // IP 范围和端口范围均有重叠,抛出 LogicException $access->addIpSegmentAllowPort('10.1.0.0/16', 90);
异常信息类似:
Rule "10.1.0.0/16:90" conflicts with existing rule "10.0.0.0/8:80-100" in the segment block list.
以下规则不会冲突,因为端口不同:
$access = new IpPortAccess(); $access ->addIpSegmentAllowPort('10.0.0.0/8', 80) ->addIpSegmentBlockPort('10.1.0.0/16', 443);
单进程性能测试
项目提供了一个简单、可复现的 CLI 微基准:
php -d opcache.enable_cli=0 benchmark/benchmark.php
压测使用的固定规则集位于 tests/Fixtures/BenchmarkRules.php,对应的正确性测试位于 tests/BenchmarkRulesTest.php。规则构建过程不计入检测耗时。
测试方式:
- 单 PHP CLI 进程执行,不启动并发进程或线程。
- 每个场景先预热,再执行 5 轮并取耗时中位数。
- 精确规则场景执行 1,000,000 次检测。
- 每个网段场景执行 100,000 次检测。
IpPortAccess网段场景统一检测端口443。- 命中 IP 使用对应目标网段内的确定性随机主机地址,而不是固定使用网络地址或第一个主机地址。
- 结果包含
isAllowAccess()方法调用和基准脚本for循环的开销。
测试数据和场景:
- 精确规则:添加一个精确黑名单 IP;
IpPortAccess同时指定端口443。用于测量哈希规则的基础检测效率。 - 256 个同前缀网段:添加从
10.0.0.0/24到10.0.255.0/24的 256 条规则,目标 IP 为最后一个网段内的10.0.255.173。用于验证同一前缀下的网段索引效率。 - 4,096 个同前缀网段:添加从
10.0.0.0/24到10.15.255.0/24的 4,096 条规则,目标 IP 为最后一个网段内的10.15.255.219。规则数量是上一场景的 16 倍,用于验证规则规模扩大后的查询稳定性。 - 1,024 个随机混合网段:使用固定初始状态
0x12345678的确定性伪随机算法,在10.0.0.0/8内生成 1,024 条不重复 CIDR,前缀随机分布在/16到/28。命中 IP 使用另一组确定性计算从对应网段的有效主机范围内选取。用于模拟地址分散、前缀长度不同的规则集合。
固定伪随机数据使每次运行使用相同规则和目标 IP,既避免只测试网段边界地址,又保证不同版本的结果可以比较。随机混合规则的 CIDR 字符串不重复,但同类型网段之间可能存在包含关系,这是允许的规则组合。
本次测试环境:
- Apple M2,8 核 CPU,16 GB 内存
- macOS 27.0.1,arm64
- PHP 8.4.23 CLI NTS
- CLI OPcache:关闭
实测结果:
| 场景 | 检测次数 | 单进程吞吐量 | 平均耗时 |
|---|---|---|---|
| IpAccess 精确 IP 命中 | 1,000,000 | 3,656,294 次/秒 | 0.274 µs/次 |
IpAccess 256 个 /24 网段命中 |
100,000 | 1,685,196 次/秒 | 0.593 µs/次 |
| IpPortAccess 精确 IP+端口命中 | 1,000,000 | 2,206,883 次/秒 | 0.453 µs/次 |
IpPortAccess 256 个 /24 网段命中 |
100,000 | 1,156,601 次/秒 | 0.865 µs/次 |
IpAccess 4,096 个 /24 网段命中 |
100,000 | 1,685,365 次/秒 | 0.593 µs/次 |
IpPortAccess 4,096 个 /24 网段命中 |
100,000 | 1,167,349 次/秒 | 0.857 µs/次 |
| IpAccess 1,024 个随机混合网段命中 | 100,000 | 290,257 次/秒 | 3.445 µs/次 |
| IpPortAccess 1,024 个随机混合网段命中 | 100,000 | 256,538 次/秒 | 3.898 µs/次 |
精确 IP 规则使用哈希键查找。网段规则按照“IPv4/IPv6 → 前缀长度 → 网络地址”建立索引,检测时每个实际使用的前缀只计算一次掩码,再通过哈希键定位网段。
256 条和 4,096 条规则均只使用 /24 一个前缀,因此二者检测耗时基本相同,说明同一前缀下增加网段数量不会退化为逐条扫描。随机混合场景使用 /16–/28 共 13 种前缀,每次检测需要分别计算这些前缀的网络地址,所以耗时高于单一前缀场景;其成本主要取决于实际使用的前缀种类数,而不是网段规则总数。
实现没有缓存查询结果。每次调用仍会校验并规范化本次传入的 IP 和端口;需要按业务请求缓存结果时,应由调用层根据自身生命周期和更新策略处理。
以上数字用于说明当前实现的性能量级,不是所有机器上的固定指标。CPU、PHP 版本、系统负载、OPcache/JIT 设置、规则数量和命中位置都会影响结果。生产环境应使用实际规则集重新运行基准。