我需要能够输出不在给定IP地址范围列表中的所有IP地址范围。
我可以使用某种算法来完成这种任务,我可以将其转换为工作代码吗?
答案 0 :(得分:1)
我认为简单解决方案的关键是要记住IP地址可以被视为多个类型的长,因此可以对它们进行排序。
我假设排除的范围以“漂亮”的方式给出,意味着没有重叠,没有与全局范围的部分重叠等等。您当然可以在以后添加此类输入检查。
在此示例中,我将所有网络范围(全局,包含,排除)作为NetworkRange
类的实例。
以下是NetworkRange
的实施。请注意方法splitByExcludedRange
和includes
。
public class NetworkRange {
private long startAddress;
private long endAddress;
public NetworkRange(String start, String end) {
startAddress = addressRepresentationToAddress(start);
endAddress = addressRepresentationToAddress(end);
}
public NetworkRange(long start, long end) {
startAddress = start;
endAddress = end;
}
public String getStartAddress() {
return addressToAddressRepresentation(startAddress);
}
public String getEndAddress() {
return addressToAddressRepresentation(endAddress);
}
static String addressToAddressRepresentation(long address) {
String result = String.valueOf(address % 256);
for (int i = 1; i < 4; i++) {
address = address / 256;
result = String.valueOf(address % 256) + "." + result;
}
return result;
}
static long addressRepresentationToAddress(String addressRep) {
long result = 0L;
String[] tokens = addressRep.split("\\.");
for (int i = 0; i < 4; i++) {
result += Math.pow(256, i) * Long.parseLong(tokens[3-i]);
}
return result;
}
public List<NetworkRange> splitByExcludedRange(NetworkRange excludedRange) {
if (this.startAddress == excludedRange.startAddress && this.endAddress == excludedRange.endAddress)
return Arrays.asList();
if (this.startAddress == excludedRange.startAddress)
return Arrays.asList(new NetworkRange(excludedRange.endAddress+1, this.endAddress));
if (this.endAddress == excludedRange.endAddress)
return Arrays.asList(new NetworkRange(this.startAddress, excludedRange.startAddress-1));
return Arrays.asList(new NetworkRange(this.startAddress, excludedRange.startAddress-1),
new NetworkRange(excludedRange.endAddress+1, this.endAddress));
}
public boolean includes(NetworkRange excludedRange) {
return this.startAddress <= excludedRange.startAddress && this.endAddress >= excludedRange.endAddress;
}
public String toString() {
return "[" + getStartAddress() + "-" + getEndAddress() + "]";
}
}
现在是计算左边包含的网络范围的类。它接受构造函数中的全局范围。
public class RangeProducer {
private NetworkRange global;
public RangeProducer(NetworkRange global) {
this.global = global;
}
public List<NetworkRange> computeEffectiveRanges(List<NetworkRange> excludedRanges) {
List<NetworkRange> effectiveRanges = new ArrayList<>();
effectiveRanges.add(global);
List<NetworkRange> effectiveRangesSplitted = new ArrayList<>();
for (NetworkRange excludedRange : excludedRanges) {
for (NetworkRange effectiveRange : effectiveRanges) {
if (effectiveRange.includes(excludedRange)) {
effectiveRangesSplitted.addAll(effectiveRange.splitByExcludedRange(excludedRange));
} else {
effectiveRangesSplitted.add(effectiveRange);
}
}
effectiveRanges = effectiveRangesSplitted;
effectiveRangesSplitted = new ArrayList<>();
}
return effectiveRanges;
}
}
您可以运行以下示例:
public static void main(String[] args) {
NetworkRange global = new NetworkRange("10.0.0.0", "10.255.255.255");
NetworkRange ex1 = new NetworkRange("10.0.0.0", "10.0.1.255");
NetworkRange ex2 = new NetworkRange("10.1.0.0", "10.1.1.255");
NetworkRange ex3 = new NetworkRange("10.6.1.0", "10.6.2.255");
List<NetworkRange> excluded = Arrays.asList(ex1, ex2, ex3);
RangeProducer producer = new RangeProducer(global);
for (NetworkRange effective : producer.computeEffectiveRanges(excluded)) {
System.out.println(effective);
}
}
输出应为:
[10.0.2.0-10.0.255.255]
[10.1.2.0-10.6.0.255]
[10.6.3.0-10.255.255.255]
答案 1 :(得分:0)
首先,我假设你的意思是你得到一个或多个不相交的CIDR范围作为输入,并且需要产生所有CIDR范围的列表,不包括任何给定的输入。为方便起见,我们进一步假设输入不包括整个IP地址空间:即0.0.0.0/0
。 (这可以通过一个特殊情况来适应,但不是很有意义。)
我之前编写的代码与此类似,虽然我无权共享代码,但我可以描述这种方法。它本质上是一种二进制搜索算法,您可以反复将整个地址空间二等分,直到您将您感兴趣的一个范围隔离开来。
将IP地址空间视为二叉树:根目录是完整的IPv4地址空间0.0.0.0/0
。它的子节点代表地址空间的一半:0.0.0.0/1
和128.0.0.0/1.
反过来,这些可以细分为创建子0.0.0.0/2
/ 64.0.0.0/2
和{{1}分别是/ 128.0.0.0/2
。一直向下继续,最后得到192.0.0.0/2,
个叶子,每个叶子代表一个2**32
(即一个地址)。
现在,将此树视为从输入列表中排除的地址空间的一部分。因此,您的任务是遍历此树,从树中的输入列表中查找每个范围,并删除输入中树的所有部分,留下地址空间的其余部分。
幸运的是,您实际上并不需要创建所有/32
个叶子。如果没有为其创建子项,则可以假定CIDR 2**32
处的每个节点都包含CIDR N
及以上的所有节点(您需要一个标记才能记住它已被细分) - 即不再是一片叶子 - 见下面的原因)。
因此,首先,整个地址空间存在于树中,但都可以由单个叶节点表示。调用树N+1
并使用单个节点excluded,
现在,考虑第一个输入范围 - 我们会调用此0.0.0.0/0.
(我将trial
用作初始14.27.34.0/24
值,以便提供示范的具体价值)。任务是从trial
中移除trial
,留下剩余的地址空间。
从excluded
节点指针设置为current
根节点开始。
开始:强>
将excluded
CIDR与trial
进行比较。如果它完全相同,那么你就完成了(但是如果你的输入范围是不相交的并且你已经从输入中排除了current
,那么这种情况永远不会发生)。
否则,如果0.0.0.0/0
是叶节点(尚未细分,意味着它代表此CIDR级别及以下的整个地址空间),请设置其细分标志,并为其创建两个子节点:指向其地址空间前半部分的current
指针,以及指向后半部分的left
指针。适当地标记每个(对于根节点的子节点,将是right
和0.0.0.0/1
)。
确定128.0.0.0/1
CIDR是否属于trial
的左侧或右侧。对于我们的初始current
值,它位于左侧。现在,如果那边的指针已经trial
,那么你又完成了(尽管如果你的输入范围是不相交的,那么#34;不会发生&#34;)。
如果NULL
CIDR 与完全等同于该方节点中的CIDR,那么只需释放节点(以及它可能拥有的任何子节点,如果你只有不相交的输入),设置指向那一侧trial
的指针,你就完成了。您只是通过从树上切下那片叶子来排除整个范围。
如果试用值与该侧节点中的CIDR不完全相同,请将NULL
设置为该侧并重新开始(即跳转到上面的开始标签)。 / p>
因此,初始输入范围为current
,您将首先将14.27.34.0/24
拆分为0.0.0.0/0
和0.0.0.0/1
。然后,您将向左下拉并将128.0.0.0/1
拆分为0.0.0.0/1
和0.0.0.0/2
然后您将再次向左下拉以创建64.0.0.0/2.
和0.0.0.0/3
依此类推,直到23次拆分后,您才会将32.0.0.0/3.
拆分为14.27.34.0/23
和14.27.34.0/24
然后您将删除左侧14.27.35.0/24.
子节点并将其指针设置为14.27.34.0/24
,留下另一个。
这将为您留下一个包含24个叶子节点的稀疏树(在您删除目标节点之后)。其余叶节点标有*:
NULL
对于每个剩余的输入范围,您将再次遍历树,在必要时将叶节点平分,通常会产生更多叶子,但总是会切掉地址空间的某些部分。
最后,您只需按照方便的顺序遍历生成的树,收集剩余叶子的CIDR。请注意,在此阶段,必须排除先前已细分的那些。例如,在上面的树中,如果你下一次处理输入范围 (ROOT)
0.0.0.0/0
/ \
0.0.0.0/1 128.0.0.0/1*
/ \
0.0.0.0/2 64.0.0.0/2*
/ \
0.0.0.0/3 32.0.0.0.0/3*
/ \
0.0.0.0/4 16.0.0.0/4*
/ \
*0.0.0.0/5 8.0.0.0/5
/ \
*8.0.0.0/6 12.0.0.0/6
/ \
*12.0.0.0/7 14.0.0.0/7
/ \
14.0.0.0/8 15.0.0.0/8*
/ \
...
/ \
*14.27.32.0/23 14.27.34.0/23
/ \
(null) 14.27.35.0/24*
(14.27.34.0/24)
,你会留下没有子节点的14.27.35.0/24
,但是它的两半都是单独剪切的,它不应该包含在输出。 (由于一些额外的复杂性,你当然可以折叠它上面的节点以适应这种情况,但是在每个节点中保留一个标志会更容易。)
答案 2 :(得分:0)
首先,您描述的内容可以简化为:
我建议使用interval tree,其中每个节点都存储一个间隔,您可以有效地插入和删除节点;并查询给定点(= IP地址)的重叠。
如果您可以保证不会有重叠,您可以使用简单的TreeSet<String>
,但必须保证(正确排序)所有字符串都使用xxx.xxx.xxx.xxx-yyy.yyy.yyy.yyy
零填充格式
一旦您的时间间隔在树中,您就可以生成所需的输出,假设没有间隔重叠,通过执行树的深度优先预先遍历遍历,并将每个被访问节点的开始和结束存储在一个列表。鉴于此列表,
请注意,0.0.0.0和255.255.255.255实际上并不是有效的可路由IP。如果您确实需要输出真实感知的IP,则应阅读相关的RFC。