よんちゅBlog

― このブログは自分用のメモや日々の問題などを共有するためのものです ―

20121005185841 お知らせ:  2013/07/17 ブログデザインをリニューアルしました。

Java〜サブネットマスクの表記変換(CIDR<->IPv4)〜

サブネットマスクを書くとき、「255.255.255.0」のようにIPアドレスのように書く方式と、
「/24」のようにCIDR(サイダー)と呼ばれる表記方法を用いる場合がありますよね。

今回はこのサブネットの、IPアドレス表記とCIDR表記の相互変換のコードを紹介します。

自分で書かなくても、どこかにありそうな気がするんだけど、あまり良さげなのが見つからなかったんですよね。

ここにCIDRからIP形式に変換するコードはあったけど、ちょっと力技かなと思いました。

CIDR表記 → IPアドレス表記へ

まずはCIDR表記をIPアドレス表記へ変換する場合です。

ご存知の通り、実際に使用されるサブネットマスクの多くは「/8」「/16」「/24」のいずれかだったりします。
というわけで、この3つに関しては計算せずにif文で判定しています。

そして、計算で導出する場合は、以下の手順で計算します。
まず、「2^(CIDR表記のサブネットマスク)-1」によってマスク値の10進表記を求めます。
次にこれを2進表記に変換します。
するとCIDR表記の値の数だけ”1”が並んだものになります。
(例えば「/24」→111111111111111111111111)

そしてこの値が32ビットになるように末尾に"0"を足します。
(例えば「/24」→11111111111111111111111100000000)

あとは、8ビットづつ区切って10進数に変換してドット(.)で連結すれば完成です。
(11111111.11111111.11111111.00000000 → 255.255.255.0)

/**
 * サブネット(CIDER表記)をIPv4形式に変換する。
 * 
 * @param subnet
 *            サブネット(CIDR表記) : 0〜32
 * @return 結果文字列(IPv4形式のサブネットマスク)
 */
public static String convertSubnetCIDR2IPv4(final int subnet) {
    if (subnet < 0 || subnet > 32) {
        throw new IllegalArgumentException(
            "引数(subnet)は 0〜32 までの数値でなければなりません。(" + subnet + ")");
    }
    // 典型的なサブネット形式は計算せずに導出
    if (subnet % 8 == 0) {
        if (subnet == 8) {
            return "255.0.0.0";
        } else if (subnet == 16) {
            return "255.255.0.0";
        } else if (subnet == 24) {
            return "255.255.255.0";
        }
    }

    // 汎用的なサブネット変換
    final StringBuilder subnetBinary = new StringBuilder(
        Long.toBinaryString((long) (Math.pow(2, subnet) - 1)));
    for (int i = subnetBinary.length(); i <= 32; i++) {
        subnetBinary.append("0");
    }

    final StringBuilder result = new StringBuilder();
    for (int i = 8, length = subnetBinary.length(); i <= length; i += 8) {
        result.append(Integer.parseInt(subnetBinary.substring(i - 8, i), 2))
            .append(".");
    }
    result.setLength(result.length() - 1);
    return result.toString();
}

IPアドレス表記 → CIDR表記

続いてIPアドレス表記をCIDR表記に変換する場合です。

計算方法は、CIDR表記→IPアドレス表記の逆ですね。
ただし、サブネットマスクの2進表記を求めた後は、先頭から"1"の数を数えることでCIDR表記の値を求めています。

また、ここを計算で導出する場合は、2進表記が32ビットになるように"0"を足して、
それを10進数(long)に変換し、+1します。
そして、そうやって求めた値の対数(低=2)をとると、計算によってCIDR表記の値を求めることができます。

低=2の対数を求める場合は「対数の変換公式」を用いましょう。
(忘れている方は思い出してね。難しくないので)

Javaで対数(低=10)を計算する場合は、java.lang.Math#log10(double) を使用します。

/**
 * サブネット(IPv4形式)をCIDR表記に変換する。
 * 
 * @param subnet
 *            サブネット(IPv4形式)
 * @return 結果(CIDR表記の値)
 */
public static int convertSubnetIP2CIDRv4(final String subnet) {
    if (subnet == null || subnet.isEmpty()) {
        throw new IllegalArgumentException("引数(subnet)は nullまたは空文字です。");
    }
    final String[] split = subnet.split("\\.");
    if (split.length != 4) {
        throw new IllegalArgumentException("引数(subnet)は IPv4形式ではありません。("
            + subnet + ")");
    }
    final StringBuilder sb = new StringBuilder();
    for (String numStr : split) {
        final int num = Integer.parseInt(numStr);
        if (num < 0 || num > 255) {
            throw new IllegalArgumentException(
                "引数(subnet)は IPv4形式ではありません。(" + subnet + ")");
        }
        sb.append(Integer.toBinaryString(num));
    }
    final String subnetBinary = sb.toString();
    int i = 0;
    while (subnetBinary.indexOf("1", i) != -1) {
        i++;
    }
    return i;
}
補足

コード内のメソッド名の「To」を「2」と表記していますが、これは誤記ではありませんよ。

これは慣例のようなものですね。
私はあまり好きではないので、今回のように変換前の対象(CIDRやIP)が大文字の場合にしか使いませんけど。

私の好き嫌いはともかくとして、こういう命名の仕方もあることは、覚えておいてもいいのではないでしょうか。