【问题标题】:Conversion from CIDR notation to IP address/subnet mask (dot-decimal)从 CIDR 表示法转换为 IP 地址/子网掩码(点十进制)
【发布时间】:2023-07-04 21:31:01
【问题描述】:
 /*
 * RFC 1518, 1519 - Classless Inter-Domain Routing (CIDR)
 * This converts from "prefix + prefix-length" format to
 * "address + mask" format, e.g. from xxx.xxx.xxx.xxx/yy
 * to xxx.xxx.xxx.xxx/yyy.yyy.yyy.yyy.
 */
static private String normalizeFromCIDR(final String netspec)
{
    final int bits = 32 - Integer.parseInt(netspec.substring(netspec.indexOf('/')+1));
    final int mask = (bits == 32) ? 0 : 0xFFFFFFFF - ((1 << bits)-1); 

    return netspec.substring(0, netspec.indexOf('/') + 1) +
            Integer.toString(mask >> 24 & 0xFF, 10) + "." +
            Integer.toString(mask >> 16 & 0xFF, 10) + "." +
            Integer.toString(mask >>  8 & 0xFF, 10) + "." +
            Integer.toString(mask >>  0 & 0xFF, 10);
}

这是 apache james 中的一个函数,用于将 ip 转换为指定格式。你能解释一下函数内部发生了什么吗?对这种位移和转换感到困惑。 提前致谢。

【问题讨论】:

    标签: java ip subnet james cidr


    【解决方案1】:

    按位运算乍一看可能不是最直观的,但一旦掌握,您就会发现它们很容易理解。我将尝试解释这段代码在172.16.0.1/23 示例中的作用,作为netspec 字符串。

    第 1 部分 - CIDR 到二进制

    目标是根据给定的 CIDR 前缀长度生成子网掩码的二进制表示。 CIDR 前缀长度只是子网掩码中的多个1 位。第一行

    final int bits = 32 - Integer.parseInt(netspec.substring(netspec.indexOf('/')+1));
    

    通过获取/ 的索引并解析其后的整数(在我的示例中为23)来查找CIDR 前缀长度。这个数字从 32 中减去,得到子网掩码中的 0 数字——这些位也称为主机位。

    在这个例子中,我们知道我们正在处理 /23 前缀,它的子网掩码应该如下所示:

    n 代表网络(B 类网络为 16 位),s 代表子网,h 代表主机。 对我们来说,网络位和子网位在功能上是相同的,但为了准确起见,我做了一个区分。我们的兴趣只是在主机位(它的数量)。

    nnnnnnnn nnnnnnnn sssssssh hhhhhhhh
    11111111 11111111 11111110 00000000
    

    最简单的方法是使用所有1s 的32 位二进制数,并用0“填充”最后9 位。这是第二行的来源:

    您可以忽略bits == 32 检查,因为它不那么相关,可能只是作为一种优化。

    //final int mask = (bits == 32) ? 0 : 0xFFFFFFFF - ((1 << bits)-1); 
    final int mask = 0xFFFFFFFF - ((1 << 9)-1); 
    

    0xFFFFFFFF 将为您提供所有1s 的 32 位二进制数。 1 左移 9 位 (1 &lt;&lt; bits) 将为您提供 512,而二进制中的 512 - 1111111111

      1 << 9                               10 00000000
    -      1                                         1
    --------------------------------------------------
                                            1 11111111
    

    当您减去这些值时,您将获得二进制子网掩码:

      0xFFFFFFFF = 11111111 11111111 11111111 11111111
    - (1 << 9)-1 =                          1 11111111
    --------------------------------------------------
                   11111111 11111111 11111110 00000000
    

    这正是我们想要的网络掩码。

    注意:这可能不是计算二进制值最直观的方法。我喜欢从全1的二进制数开始,而有符号整数中的数字的十进制值为-1。然后我只是将它的主机位数向左移动,就是这样。 (此外,如果您正在处理大于 32 位的整数,您可以使用 0xFFFFFFFF 对其进行屏蔽):

    (-1 << 9) & 0xFFFFFFFF
    

    第 2 部分 - 二进制到点分十进制

    其余代码将二进制值转换为点分十进制表示 - 255.255.254.0。

    return netspec.substring(0, netspec.indexOf('/') + 1) +  // part of the netspec string before '/' -> IP address
            Integer.toString(mask >> 24 & 0xFF, 10) + "." +  //                         11111111 & 0xFF = 0xFF
            Integer.toString(mask >> 16 & 0xFF, 10) + "." +  //                 1111111111111111 & 0xFF = 0xFF
            Integer.toString(mask >>  8 & 0xFF, 10) + "." +  //         111111111111111111111110 & 0xFF = 0xFE
            Integer.toString(mask >>  0 & 0xFF, 10);         // 11111111111111111111111000000000 & 0xFF = 0x00
    

    return 语句由几个连接的字符串组成,以 IP 地址开头,然后是每个八位字节的十进制表示。二进制掩码右移(4-n)*8 位(其中n 是八位字节数),并使用二进制与 0xFF 只得到最后 8 位,然后由Integer.toString 解析。

    结果是172.16.0.1/255.255.254.0

    【讨论】:

      【解决方案2】:

      将 cidr 表示法转换为二进制的另一种方法:

      input = '1.2.3.4/5'
      cidr = input.split('/') 
      
      bin_mask = '1' * cidr + '0' * (32 - cidr)
      

      【讨论】:

        最近更新 更多