Java中IP地址过滤器的内存数据结构的最佳选择

时间:2011-11-29 19:04:36

标签: java filter ip in-memory

我的文件是CIDR格式,如192.168.1.0/24,它被转换为这两列结构

3232236030 3232235777

使用以下代码进行每个字符串IP地址转换:

String subnet = "192.168.1.0/24";
SubnetUtils utils = new SubnetUtils(subnet);

Inet4Address a = (Inet4Address) InetAddress.getByName(utils.getInfo().getHighAddress());
long high = bytesToLong(a.getAddress());
Inet4Address b = (Inet4Address) InetAddress.getByName(utils.getInfo().getLowAddress());
long low = bytesToLong(b.getAddress());

private static long bytesToLong(byte[] address) {
   long ipnum = 0;
   for (int i = 0; i < 4; ++i) {
       long y = address[i];
       if (y < 0) {
           y += 256;
       }
       ipnum += y << ((3 - i) * 8);
   }
   return ipnum;
}

考虑到(low high : 3232236030 3232235777)的条目超过500万 此外,还会有交叉点,因此IP可以来自多个范围。只是第一个不仅仅是OK 数据是只读的 找到ipToBefiltered所属范围的最快方法是什么?该结构将完全在内存中,因此无需数据库查找。

UPDATE:

我找到了这个Peerblock项目(它有超过百万的下载,所以我认为它必须有一些快速的算法): http://code.google.com/p/peerblock/source/browse/trunk/src/pbfilter/filter_wfp.c

有谁知道项目使用什么技术来创建范围列表而不是搜索它们?

5 个答案:

答案 0 :(得分:7)

  

当谈到它时,我只需要知道IP是否存在于任何5M范围内。

我会考虑一个 n-ary 树,其中n = 256,并使用虚线地址而不是转换后的整数。

顶级是256个对象的数组。 null条目表示“否”没有包含地址的范围,因此假设您的示例192.168.1.0/24数组[192]将包含一个对象,但数组[100]可能为空,因为没有范围是为任何100.xxx/n

定义

存储对象包含(引用)另一个数组[256]和范围说明符,只会设置其中一个,因此192.0.0.0/8最终会有一个范围说明符,表示该范围内的所有地址要过滤。这将允许192.255.0.0/10之类的地址,其中地址的前10位是重要的1100 0000 11xx xxxx - 否则你需要检查第二级数组中的下一个八位字节。

最初将重叠范围(如果有的话)合并到更大的范围内......例如3 .. 107 .. 16变为3 .. 16 ...允许这样做,因为您不需要将给定的IP与 范围定义它的相关联。

这应该不超过8次比较。每个八位字节最初直接用作索引,然后是null的比较,终端节点的比较(是范围还是指向下一个树级别的指针)

如果每个 IP地址都在过滤范围内,最坏情况下内存消耗理论上是4 GB (256 ^ 4),但当然会合并到一个范围内,所以实际上只有1个范围宾语。更现实的最坏情况可能更像(256 ^ 3)或16.7 MB。真实世界的使用可能会使每个级别的大多数数组[256]节点都为空。

这基本上类似于霍夫曼/前缀编码。一旦找到答案(范围),最短的不同前缀就会终止,因此通常会有< 4的平均值进行比较。

答案 1 :(得分:1)

我会使用int的排序数组(基地址)和另一个相同大小的数组(结束地址)。这将使用5M * 8 = 40 MB。第一个IP是基础,第二个IP是范围中的最后一个地址。您需要删除交叉点。

要查找地址是否已过滤为二进制搜索O(日志N),如果不是完全匹配,请检查它是否小于(或等于)上限。

答案 2 :(得分:1)

我在Vuze (aka azureus)项目中发现了这个二进制斩波算法:

public IpRange isInRange(long address_long) {
    checkRebuild();

    if (mergedRanges.length == 0) {
        return (null);
    }

    // assisted binary chop

    int bottom = 0;
    int top = mergedRanges.length - 1;
    int current = -1;

    while (top >= 0 && bottom < mergedRanges.length && bottom <= top) {

        current = (bottom + top) / 2;

        IpRange e = mergedRanges[current];

        long this_start = e.getStartIpLong();
        long this_end = e.getMergedEndLong();

        if (address_long == this_start) {
            break;
        } else if (address_long > this_start) {

            if (address_long <= this_end) {
                break;
            }

            // lies to the right of this entry

            bottom = current + 1;

        } else if (address_long == this_end) {
            break;
        } else {
            // < this_end

            if (address_long >= this_start) {
                break;
            }
            top = current - 1;
        }
    }

    if (top >= 0 && bottom < mergedRanges.length && bottom <= top) {

        IpRange e = mergedRanges[current];

        if (address_long <= e.getEndIpLong()) {
            return (e);
        }

        IpRange[] merged = e.getMergedEntries();

        if (merged == null) {
            //inconsistent merged details - no entries
            return (null);
        }

        for (IpRange me : merged) {
            if (me.getStartIpLong() <= address_long && me.getEndIpLong() >= address_long) {
                return (me);
            }
        }
    }
    return (null);
}

似乎表现得非常好。如果您对事情了解得更快,请告诉我。

答案 3 :(得分:1)

如果您只有一个CIDR地址(或它们的列表),并且您想要检查某个ipAddress是否在该CIDR(或CIDR列表)的范围内,只需定义一组SubnetUtils对象。

除非您正在过滤非常大的N个地址,否则这是所有字符串比较,并且执行速度非常快。您不需要根据高/低位和所有复杂的Jazz构建二叉树。

String subnet = "192.168.1.0/24";
SubnetUtils utils = new SubnetUtils(subnet);
//...
//for each subnet, create a SubnetUtils object
Set<SubnetUtils> subnets = getAllSubnets();
//...

使用Guava Predicate过滤不在您的子网范围内的ipAddresses:

   Set<String> ipAddresses = getIpAddressesToFilter();
   Set<String> ipAddressesInRange = 
       Sets.filter(ipAddresses, filterIpsBySubnet(subnets))


   Predicate<String> filterIpsBySubnet(final Set<SubnetUtils> subnets){
       return new Predicate<String>() {
            @Override
            public boolean apply(String ipAddress) {
                for (SubnetUtils subnet : subnets) {
                    if (subnet.getInfo().isInRange(ipAddress)) {
                        return true;
                    }
                }
                return false;
            }
        };
   }

现在,如果IP位于任何子网中,您就拥有了一个简单的过滤器,并且您不必构建一个必须进行单元测试的数据结构。如果这不够高效,那么请进行优化。不要过早地优化:)

答案 4 :(得分:0)

这是答案的开始,当我获得更多的空闲时间时,我会回来

<强>设定:

  1. 按起始编号对范围进行排序。
  2. 由于这些是IP地址,我假设没有任何范围重叠。如果存在重叠,则应该运行列表合并范围并修剪不必要的范围(例如,如果范围为1 - 10,则可以修剪范围5 - 7)。
    1. 要合并或修剪,请执行此操作(假设范围a紧接在范围b之前):
      1. 如果b.end&lt; a.end然后范围b是范围a的子集,您可以删除范围b。
      2. 如果b.start&lt; b.end和b.end&gt;然后你可以合并范围a和b。设置a.end = b.end然后删除范围b。