【问题标题】:How to get all IP addresses that are not in a given range of IP addresses如何获取不在给定 IP 地址范围内的所有 IP 地址
【发布时间】:2018-10-08 01:16:51
【问题描述】:

我需要能够输出不在给定 IP 地址范围列表中的所有 IP 地址范围。

有某种算法可以用于这种任务,我可以将其转换为工作代码?

  • 基本上我将使用 Salesforce Apex 代码,因此如果给定示例可能,任何类似 JAVA 的语言都可以。

【问题讨论】:

  • 如何指定范围? X.X.X.X/Y?
  • @DavidEisenstat - X.X.X.X 到 Y.Y.Y.Y
  • @GoldenAxe 您的任务仅与 IPv4 有关吗? (意味着没有 IPv6 地址)
  • 您要解决的更广泛的问题是什么?平台本身可能会根据您的尝试为您提供帮助。
  • 有趣的问题

标签: algorithm network-programming ip salesforce apex


【解决方案1】:

我认为一个简单解决方案的关键是记住 IP 地址可以被视为长类型的数字,因此可以对其进行排序。

我假设排除的范围以“很好”的方式给出,这意味着没有重叠,没有与全局范围的部分重叠等等。您当然可以稍后添加此类输入检查。

在本例中,我将所有网络范围(全局、包含、排除)作为 NetworkRange 类的实例。

以下是NetworkRange的实现。注意splitByExcludedRangeincludes这两个方法。

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]

【讨论】:

    【解决方案2】:

    首先,我假设您的意思是您获得一个或多个不相交的 CIDR 范围作为输入,并且需要生成所有 CIDR 范围的列表,其中不包括作为输入给出的任何 CIDR 范围。为方便起见,让我们进一步假设输入不包括整个 IP 地址空间:即0.0.0.0/0。 (这可以通过一个特殊情况来解决,但没有太大意义。)

    我以前写过类似的代码,虽然我不能随意分享代码,但我可以描述方法。它本质上是一种二分搜索算法,在该算法中,您重复地平分整个地址空间,直到您隔离出您感兴趣的一个范围。

    将 IP 地址空间视为二叉树:根是完整的 IPv4 地址空间0.0.0.0/0。它的子代分别代表地址空间的一半:0.0.0.0/1128.0.0.0/1. 这些子代又可以细分为分别创建子代0.0.0.0/2 / 64.0.0.0/2128.0.0.0/2 / 192.0.0.0/2,。一直继续下去,你会得到2**32 叶子,每个叶子代表一个/32(即一个地址)。

    现在,将这棵树视为从输入列表中排除的地址空间部分。所以你的任务是遍历这棵树,从树中的输入列表中找到每个范围,并剪掉你输入中树的所有部分,留下地址空间的剩余部分。

    幸运的是,您实际上不需要创建所有 2**32 叶子。如果没有为其创建子节点,则可以假设 CIDR N 的每个节点包括 CIDR N+1 及以上的所有节点(您需要一个标志来记住它已经被细分 - ie 不再是一片叶子——原因见下文)。

    因此,首先,整个地址空间都存在于树中,但都可以由单个叶节点表示。调用树excluded,并用单个节点0.0.0.0/0.初始化它

    现在,考虑第一个输入范围——我们将其称为trial(我将使用14.27.34.0/24 作为初始trial 值,只是为了提供一个具体的值用于演示)。任务是从excluded 中删除trial,留下剩余的地址空间。

    current 节点指针设置为 excluded 根节点开始。

    开始:

    trial CIDR 与current 进行比较。如果它是相同的,那么你就完成了(但如果你的输入范围不相交并且你已经从输入中排除了0.0.0.0/0,这永远不会发生)。

    否则,如果current是一个叶子节点(还没有被细分,意味着它代表了这个CIDR级别及以下的整个地址空间),设置它的细分标志,并为其创建两个子节点:一个@ 987654347@ 指向其地址空间的前半部分,right 指向后半部分。适当地标记其中的每一个(对于根节点的子节点,将是0.0.0.0/1128.0.0.0/1)。

    确定trial CIDR 是在current 的左侧还是右侧。对于我们最初的trial 值,它位于左侧。现在,如果那一侧的指针已经是NULL,那么你就完成了(尽管如果你的输入范围不相交,那“不可能发生”)。

    如果trial CIDR 完全等同于该节点中的 CIDR,则只需释放该节点(以及它可能拥有的任何子节点,如果有,则应该没有只有不相交的输入),将指针设置到那一侧NULL,你就完成了。您刚刚通过从树上剪下那片叶子来排除整个范围。

    如果试验值与该侧节点中的CIDR不完全相等,则将current设置为该侧并重新开始(即跳转到上面的开始标签)。

    因此,初始输入范围为14.27.34.0/24,您将首先将0.0.0.0/0 拆分为0.0.0.0/1128.0.0.0/1。然后,您将在左侧下拉并将0.0.0.0/1 拆分为0.0.0.0/264.0.0.0/2. 然后您将再次下拉到左侧以创建0.0.0.0/332.0.0.0/3. 等等,直到经过23 次拆分,您然后将14.27.34.0/23 拆分为14.27.34.0/2414.27.35.0/24. 然后删除左侧的14.27.34.0/24 子节点并将其指针设置为NULL,留下另一个。

    这将为您留下一棵包含 24 个叶节点的稀疏树(在您删除目标节点之后)。剩下的叶子节点用*标记:

                                 (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)
    

    对于每个剩余的输入范围,您将再次遍历树,必要时将叶节点一分为二,通常会产生更多叶,但总是会切掉地址空间的一部分。

    最后,您只需按照方便的顺序遍历生成的树,收集剩余叶子的 CIDR。请注意,在此阶段您必须排除之前已细分的那些。例如,在上面的树中,如果您接下来处理输入范围14.27.35.0/24,您将留下没有子级的14.27.34.0/23,但它的两半都被分别切掉,不应包含在输出中。 (如果有一些额外的复杂情况,您当然也可以折叠其上方的节点以适应这种情况,但在每个节点中保留一个标志会更容易。)

    【讨论】:

    • 如前所述,格式为 X.X.X.X 到 Y.Y.Y.Y,您可以将其视为 IP 范围对象的列表,列表中的每个项目都包含 Start-IP 和 End-IP。我正在考虑按升序对列表进行排序,在其上循环,然后创建一个新列表,其范围介于两者之间。列表开头和列表结尾的特殊情况。这个解决方案有效吗? * 假设我们没有重叠。
    • 此解决方案适用于任何组范围,因为任何范围都可以分解为有效 CIDR 格式范围的列表——尽管列表中的个别成员可能最终是/32(即单主机)。
    【解决方案3】:

    首先,您所描述的可以简化为:

    • 您的区间格式为 x.x.x.x - y.y.y.y
    • 您想要输出尚未在此范围内“采用”的间隔。
    • 您希望能够有效地添加或删除间隔

    我建议使用interval tree,其中每个节点存储一个区间,您可以高效地插入和删除节点;并查询给定点(= IP 地址)的重叠。

    如果您可以保证不会有重叠,您可以改用简单的TreeSet&lt;String&gt;,但是您必须保证(为了正确排序)所有字符串都使用xxx.xxx.xxx.xxx-yyy.yyy.yyy.yyy 零填充格式。

    一旦您的区间位于树中,您就可以生成所需的输出,假设没有区间重叠,方法是对树执行深度优先的前序遍历,并将每个访问节点的开始和结束存储在一个列表。鉴于此列表,

    • 在开头预置 0.0.0.0
    • 在末尾追加 255.255.255.255
    • 删除所有重复的 ip(它们将在列表中强制相邻)
    • 成对使用(数量始终为偶数),您就有了免费 IP 的间隔,完全排序。

    请注意,0.0.0.0255.255.255.255 实际上不是有效的可路由 IP。如果你真的需要输出真实世界感知的 IP,你应该阅读相关的 RFC。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-09
      • 1970-01-01
      • 1970-01-01
      • 2011-12-01
      • 2013-08-22
      • 2014-12-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多