我正在解析Apache日志,并想检查一个IP地址是否属于~300个可能的子网列表(https://github.com/client9/ipcat)。
我可以在这里做些什么吗?
答案 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字符串初始转换为整数之后,这是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}}