检查IP地址是否在多个可能的子网中

时间:2016-08-17 12:38:23

标签: java networking network-programming ip

我正在解析Apache日志,并想检查一个IP地址是否属于~300个可能的子网列表(https://github.com/client9/ipcat)。

  • 互联网流量有很多不同的IP(我不知道他们属于哪个子网)。
  • 我正在使用Apache Commons SubnetUtils包来存储子网。
  • 将所有可能的IP存储在300个子网的散列图中会占用太多内存。
  • 为每个IP迭代每个SubnetUtil(即使使用HashMap缓存进行先前的查找)也非常慢。

我可以在这里做些什么吗?

3 个答案:

答案 0 :(得分:1)

您可以使用子网构建树结构 - 它可以是一点一滴的。这将把支票从300减少到最多32(假设是IPv4),但在大多数情况下要少得多。 (因为它在几位之后不匹配,或者在子网网络掩码的平均长度上匹配)

这是一个执行此操作的简单二叉树实现。您可能希望使用某些函数来装饰它,以便以更常见的"a.b.c.d/e"格式解析子网。

public class SubnetTree {
    private SubnetTree one, zero;
    private boolean terminating;

    public void addSubnet(int net, int bits) {
        if (terminating) {
            // If this node is already terminating, then no need to add
            // subnets that are more specific
            return;
        }
        if (bits > 0) {
            boolean bit = ((net >>> 31) & 1) == 1;
            if (bit) {
                if (one == null) {
                    one = new SubnetTree();
                }
                one.addSubnet(net << 1, bits - 1);
            } else {
                if (zero == null) {
                    zero = new SubnetTree();
                }
                zero.addSubnet(net << 1, bits - 1);
            }
        } else {
            terminating = true;
        }
    }

    public boolean isInRange(int address) {
        if (terminating) {
            return true;
        }
        boolean bit = ((address >>> 31) & 1) == 1;
        if (bit) {
            if (one == null) {
                return false;
            } else {
                return one.isInRange(address << 1);
            }
        } else {
            if (zero == null) {
                return false;
            } else {
                return zero.isInRange(address << 1);
            }
        }
    }
}

此代码的一个非常简单的测试:

public static void main(String[] args) {
    SubnetTree tree = new SubnetTree();
    tree.add(Integer.parseUnsignedInt("01100110000000000000000000000000", 2), 8);
    System.out.println("true: " + tree.isInRange(Integer.parseUnsignedInt("01100110000000000000100010000101", 2)));
    System.out.println("false: " + tree.isInRange(Integer.parseUnsignedInt("01101110000000000000100010000101", 2)));

    tree.add(Integer.parseUnsignedInt("01001110000000000000000000000000", 2), 6);
    System.out.println("true: " + tree.isInRange(Integer.parseUnsignedInt("01100110000000000000100010000101", 2)));
    System.out.println("false: " + tree.isInRange(Integer.parseUnsignedInt("01101110000000000000100010000101", 2)));
    System.out.println("true: " + tree.isInRange(Integer.parseUnsignedInt("01001110100000000000000000000000", 2)));
    System.out.println("true: " + tree.isInRange(Integer.parseUnsignedInt("01001100100000000000000000111111", 2)));
}

答案 1 :(得分:1)

您基本上想要为所有子网构建Trie(前缀树)。在其中搜索将非常快,最坏的情况是最长子网前缀的长度(最终为32)。在记忆方面,它也非常有效。

在二进制表示中考虑您的子网,因此树将存储它。然后,IP地址也应转换为二进制,并将树用于搜索。到达叶子时,检查它是否是有效的子网。

这是一个非常完整的实现/示例:

import java.util.Arrays;

public class CheckIpAdrBelongsToSubnet {
  private static final String [] CIDR_SUBNETS = {"192.5.0.0/16", "10.10.0.0/16"};

  public static void main(String ... ips) {
    BinaryTrieNode root = createBinaryTrieFromCidrs(CIDR_SUBNETS);

    for(String ip :ips) {
      System.out.println(ip + " belongs to a subnet in list: " + ipBelongsToCidrs(ip, root));
    }
  }

  static boolean ipBelongsToCidrs(String ipv4, BinaryTrieNode root) {
    BinaryIpV4 bipv4 = new BinaryIpV4(ipv4);
    BinaryTrieNode current = root;
    for(boolean b : bipv4.getBinIp()) {
      BinaryTrieNode nextCurr = current.getNode(b);
      if(nextCurr == null) {
        return current.isEndOfValidPrefix();
      } else {
        current = nextCurr;
      }
    }
    return false;
  }

  static BinaryTrieNode createBinaryTrieFromCidrs(String [] cidrs) {
    BinaryTrieNode root = new BinaryTrieNode();

    for (String cidr : cidrs) {
      String ipv4 = cidr.split("/")[0];
      int prefixLength = Integer.parseInt(cidr.split("/")[1]);
      BinaryIpV4 bipv4 = new BinaryIpV4(ipv4, prefixLength);

      BinaryTrieNode current = root;
      for(boolean b : bipv4.getBinIp()) {
        BinaryTrieNode nextCurr = current.getNode(b);
        if(nextCurr == null) {
          nextCurr = new BinaryTrieNode();
          current.setNode(b, nextCurr);
        }
        current = nextCurr;
      }
      current.setEndOfValidPrefix(true);
    }

    return root;
  }

  public static class BinaryIpV4 {
    boolean [] binIp;

    BinaryIpV4(String ipv4) {
      translateCidrToBinPrefix(ipv4,32);
    }

    BinaryIpV4(String ipv4, int prefixLength) {
      translateCidrToBinPrefix(ipv4,prefixLength);
    }

    void translateCidrToBinPrefix(String ipv4, int prefixLength) {
      String [] bytes = ipv4.split("\\.");
      binIp = new boolean [prefixLength];
      int idx = 0;

      for(String b : bytes) {
        int by = Integer.parseUnsignedInt(b);
        String binByte = getBinaryString(by);

        for(int i = 0; i < binByte.length() ; i++) {
          binIp[idx++] = binByte.charAt(i)=='1';

          if(idx >= prefixLength) {
            return;
          }
        }
      }
    }

    public boolean[] getBinIp() {
      return binIp;
    }

    String getBinaryString(int b) {
      return String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
    }

    public String toString() {
      return Arrays.toString(binIp);
    }
  }

  public static class BinaryTrieNode {
    private boolean isEndOfValidPrefix=false;

    private BinaryTrieNode zeroNode;
    private BinaryTrieNode oneNode;

    public boolean isEndOfValidPrefix() {
      return isEndOfValidPrefix;
    }
    public void setEndOfValidPrefix(boolean isEndOfValidPrefix) {
      this.isEndOfValidPrefix = isEndOfValidPrefix;
    }
    public BinaryTrieNode getNode(boolean b) {
      return b?oneNode:zeroNode;
    }
    public void setNode(boolean b, BinaryTrieNode node) {
      if(b) {
        this.oneNode = node;
      } else {
        this.zeroNode = node;
      }

    }
  }
}

答案 2 :(得分:0)

这就是我要做的事情:

  • 事先设置一个策略,确保没有子网可能与任何其他子网重叠。
  • 通过启动IP(一次)按升序对子网进行排序。
  • 对于每个IP,执行数组的二进制搜索,返回小于或等于IP的最后一个IP。
  • 检查一个子网。

在将IP字符串初始转换为整数之后,这是O(log2(n))简单的数值比较,其中n是300(所以大约9)加上单个按位AND和一个额外的数字比较。

要将字符串转换为C中的整数,您可以这样做:

uint32_t mask_val = ~((1 << (32 - nbits)) - 1);
// e.g. a mask of 30 gives you ~[[1 << 2 == 0b100] - 1 = 0b11] = 0b111...100

每个IP(子网和您正在检查的子网)。对于面具:

if (ip & mask == subnet) { // match }

我认为大多数其他语言都有类似的功能。

然后你会将它们与:

进行比较
{{1}}