算法题实战 — 大规模黑名单IP匹配

算法是许多IT公司面试时很重要的一个环节,但也有很多人抱怨实际工作中很少碰到,实用性不高。本文描述了我们公司在面试时常用的一道题目,虽然底层用了非常简单的算法,但却是具体工作中比较容易见到的场景:大规模黑名单 ip 匹配。同时用我们在安全网关中开发的例子来做些验证。

一、问题和场景

问题:有海量的 ip 网段名单,需要快速的验证某一个 ip 是否属于其中任意一个网段。

这其实是一个比较普遍的问题,以我们的安全网关为例,至少在以下场景中有需要:

场景一:单纯的黑白名单匹配

对于网关来说,黑白名单匹配是基本功能:

  • 内部 ip 需要白名单 bypass。按照公司的规模和地域所在,这里可能会有大量的白名单。
  • 攻击 ip 需要黑名单 block。目前的互联网,各种扫描和攻击还是比较猖獗的,可以很容易的获得大量黑名单 ip,需要进行实时封禁。
  • 类似的可以参考 nginx 的黑名单功能,通过 deny 语句 “deny 192.168.1.0/24;” 可以定义一批 ip 网段,用来做访问控制。

场景二:真实ip获取

真实 ip 获取对有些网站来说其实是一个比较麻烦的问题,因为流量可能有不同的来源路径:

  • 浏览器–>网关。这种直接取 remote_address, 即 tcp 的远端地址;
  • 浏览器–>lb–>网关。中间可能有别的负载均衡,一般靠 XFF 头来识别;
  • 浏览器–>cdn–>lb–>网关。有些流量走了 cdn 或者云 waf,需要对 XFF 头特别处理,识别出 cdn 的 ip;
  • 浏览器–>cdn–>lb–>…–>lb–>网关。实际场景中,受到重定向,内部多层网关的影响,可能会有比较复杂的场景。

类似的可以参考 nginx 的真实 ip 功能, 原理也比较简单,通过类似 “set_real_ip_from 192.168.1.0/24;” 的语句可以设置内部 ip 名单,这样在处理 XFF 头的时候,从后往前找,递归模式下寻找第一个不是内部 ip 的值,即真实 ip。这就回归到本文的问题上来。

场景三:流量标注

这部分功能常由后端的业务模块自行实现,我们在开发产品中希望能在请求进来的时候做一些自动标注,减轻后端的负担,比较有用的如:

  • ip 归属地判断。ip 归属地一般是由数十万网段组成的索引,需要进行快速判断;
  • 基站标注。目前大量使用 4g 上网,所以基站 ip 必须小心对待,而基站数据也是大量的 ip 网段;
  • 服务器标注。目前较多的攻击来自于云服务器,这些标注对后台的安全和风控业务有协助。云服务器列表也通过海量 ip 网段列表来展现。

以上场景描述了海量 ip 网段列表匹配的一些应用场景,还是比较容易碰到的。

二、算法描述

算法一: hashmap

绝大部分人第一反应是通过 hashmap 来做匹配,理论上可以实现(将网段拆分为独立的 ip),但基本不可用:

  • 网段的掩码不一定是24位,可以是32内的任一数字,所以如果要保证普遍性的话,需要完全拆成独立的 ip;
  • 哪怕是真实 ip 获取这样常见的场景,我们在客户这边碰到,由于使用了多家 cdn 厂家,cdn 网段有1300+,假设都为24位掩码的 c 类地址,也会有332800+的 ip,做成 hashmap 将是大量的内存开销;
  • 由于网关一般是通过多进程或者多实例做水平扩展的,这个内存浪费也会成倍增加。

所以 hashmap 的方式所以查询高效,但在实现层来说不太可行。

算法二:对网段列表进行顺序匹配

目前可以看到一些开源的实现大都采用这种方式,比如场景段落描述的 nginx 两个功能模块,可以再 accss 模块和 realip 模块发现都是将配置存储为 cidr 列表,然后逐个匹配;另外一个实现是 openresty 的 lua-resty-iputils 模块,这个代码看起来比较直观些:

  1. local function ip_in_cidrs(ip, cidrs) 
  2.  local bin_ip, bin_octets = ip2bin(ip) 
  3.  if not bin_ip then 
  4.  return nil, bin_octets 
  5.  end 
  6.  for _,cidr in ipairs(cidrs) do 
  7.  if bin_ip >= cidr[1] and bin_ip <= cidr[2] then 
  8.  return true 
  9.  end 
  10.  end 
  11.  return false 
  12. end 

开源的实现在应付绝大多数简单场景足够可用,但后面的测试可以看到,当ip网段数量上升的时候,性能还是欠缺。

算法三:二分查找

实际的算法其实很简单,二分查找即可,假设这些 ip 网段都是互不相邻的,采用类似 java 的二分查找即可,如图:

假设有 A, B, C, D 四个互不相邻的 ip 网段,每个网段可以转化为两个数字:起始ip的整型表示和终止 ip 的整型表示;比如 0.0.0.0/24 可以转化为 [0, 255]。这样四个网段转化为 8 个数字,可以进行排序,由于网段是互不相邻的,所以一定是图上这种一个 ip 段接一个 ip 段的情形。这样匹配的算法会比较简单:

  • 将被查询 ip 转化为数字,并在数组中进行二分查找;
  • 参考 java 的二分实现,当查询命中时,直接返回命中数字的index;当查询未命中时,返回一个负数,其绝对值表示了其插入位置(具体实现需略作变化,这里略过不计);
  • 第二步如果返回值为正数,恭喜你,找到了,直接命中;
  • 第二步如果返回的为负数,同时插入坐标为奇数(1, 3, 5, 7),说明插入点正好在一个网段之内,说明命中;
  • 第二步如果返回的为负数,同时插入坐标为偶数(0, 2, 4, 6, 8),说明插入点正好在两个网段之间,说明此 ip 与所有网段都不命中;
  • 证毕收工。

所以整个算法非常简单,不过这里假设了网段之间是互不相邻的,这个很容易被忽视掉,下面做一些简单说明。

任意两个网段 A 和 B,可能有三种关系:

  • 完全不相邻。A 和 B 没有任何重复的部分。
  • 相包含,即 A 包含 B 或 B 包含 A。这种情形在数据预处理的时候可以发现并排除掉(只保留大的网段)。
  • A 和 B 相交,但并不包含。即两个网段存在相互交错的情形,下面通过图形说明此种情况不成立。

上图描述了任意两个网段:

  • “*”表示掩码
  • 两个网段,共32位,其中子网部分,前面 X 个连续 bit 是相同的
  • 第一个网段剩余 Y 个 bit,第二个网段剩余 Z 个 bit

所以:

  • 假设 Y == Z == 0, 表示两个网段完全相等,否则
  • Y == 0 && Z != 0, 说明第一个网段包含第二个网段;Y != 0 && Z == 0, 则第二个网段更大
  • Y != 0 && Z != 0,就是图上的直观表示,由于网段中的 ip 只能是*号部分的变化,所以两个网段不可能有相同的 ip,因为中间至少有几位是不同的

因此,如果对原始数据进行一定的预处理,二分查找是安全有效的方式。

三、测试数据

最近手机出的有点多,我们也跟风跑个分:

  • 测试采用 Raspberry Pi 3 Model B, 4核 1.2GHz CPU, 1G 内存。
  • 通过 wrk 进行持续30s,50个连接的性能测试。

测试一:基准测试

  1. Running 30s test @ http://10.0.0.5/ 
  2. 12 threads and 50 connections 
  3. Thread Stats Avg Stdev Max +/- Stdev 
  4. Latency 6.54ms 4.80ms 194.75ms 99.29% 
  5. Req/Sec 617.22 56.76 1.05k 80.42% 
  6. Latency Distribution 
  7. 50% 6.22ms 
  8. 75% 6.99ms 
  9. 90% 7.78ms 
  10. 99% 10.74ms 
  11. 221915 requests in 30.10s, 40.62MB read 
  12. Requests/sec: 7373.42 
  13. Transfer/sec: 1.35MB 

测试二:10000个黑名单+hashmap

  1. Running 30s test @ http://10.0.0.5/block_ip_1w 
  2. 12 threads and 50 connections 
  3. Thread Stats Avg Stdev Max +/- Stdev 
  4. Latency 7.75ms 2.34ms 94.11ms 85.57% 
  5. Req/Sec 512.72 68.86 780.00 74.28% 
  6. Latency Distribution 
  7. 50% 7.21ms 
  8. 75% 8.36ms 
  9. 90% 10.63ms 
  10. 99% 14.07ms 
  11. 184298 requests in 30.09s, 32.16MB read 
  12. Requests/sec: 6125.35 
  13. Transfer/sec: 1.07MB 

测试三:10000个黑名单+lua-resty-utils 模块顺序查找

  1. Running 30s test @ http://10.0.0.5/block_iputils_1w 
  2. 12 threads and 50 connections 
  3. Thread Stats Avg Stdev Max +/- Stdev 
  4. Latency 162.93ms 100.27ms 1.96s 95.22% 
  5. Req/Sec 27.47 12.28 150.00 66.46% 
  6. Latency Distribution 
  7. 50% 155.88ms 
  8. 75% 159.40ms 
  9. 90% 161.54ms 
  10. 99% 670.13ms 
  11. 9164 requests in 30.09s, 1.60MB read 
  12. Socket errors: connect 0, read 0, write 0, timeout 11 
  13. Requests/sec: 304.52 
  14. Transfer/sec: 54.41KB 

测试四:10000个黑名单+二分查找

  1. Running 30s test @ http://10.0.0.5/block_ipcidr_bin_1w 
  2. 12 threads and 50 connections 
  3. Thread Stats Avg Stdev Max +/- Stdev 
  4. Latency 9.60ms 6.78ms 196.80ms 97.53% 
  5. Req/Sec 427.92 82.80 0.89k 60.15% 
  6. Latency Distribution 
  7. 50% 8.45ms 
  8. 75% 10.94ms 
  9. 90% 12.55ms 
  10. 99% 18.58ms 
  11. 153892 requests in 30.10s, 26.85MB read 
  12. Requests/sec: 5112.69 
  13. Transfer/sec: 0.89MB 

通过测试数据,可以看到二分搜索可以达到接近于基于 hash 的性能,但内存消耗等会少很多;而简单的顺序遍历会带来数量级的性能下降。

【本文是IDC.NET专栏机构“岂安科技”的原创文章,转载请通过微信公众号(bigsec)联系原作者】

戳这里,看该作者更多好文

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/125099.html<

(0)
运维的头像运维
上一篇2025-02-23 14:28
下一篇 2025-02-23 14:29

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注