Cover Image for Javaで文字列がネットワークを表すCIDR形式として正しいか判定する

Javaで文字列がネットワークを表すCIDR形式として正しいか判定する

この記事は最終更新日から5年以上が経過しています。

TL;DR

  • 正規表現チェックだけではダメ
  • IPアドレスは2進数で扱える
  • IPアドレス と サブネットマスクを OR した結果が サブネットマスクと同一なら問題ない

やりたいこと

IPアドレス/ciderBlock 形式で与えられて文字列のIPアドレス部分がネットワークアドレスを表しているかチェックしたい 簡単にいうと↓こうなってほしい

  • OK: 192.0.2.0/24
  • NG: 192.0.2.1/24

解決方法

前提:正規表現等でチェックできる部分はチェック完了している IPアドレスは2進数として扱えるので以下のようにチェックできる

OKになる例: 192.0.2.0/24 をチェックする

  1. IPアドレス と ciderBlock に分解する
    • ipAddress: 192.0.2.0
    • ciderBlock:24
  2. ciderBlock をサブネットマスクに変換する
    • ciderBlock: 24 -> subnetMask:255.255.255.0
  3. IPアドレス・サブネットマスクを2進数で表してORを取る
    • OR を とった結果が サブネットマスクと同一なのでネットワークアドレスを表している
ip          : 11000000.00000000.00000010.00000000
subnet      : 11111111.11111111.11111111.00000000
ip OR subnet: 11111111.11111111.11111111.00000000

NGになる例: 192.0.2.1/24 をチェックする

  1. IPアドレス と ciderBlock に分解する
    • ipAddress: 192.0.2.1
    • ciderBlock:24
  2. ciderBlock をサブネットマスクに変換する
    • ciderBlock: 24 -> subnetMask:255.255.255.0
  3. IPアドレス・サブネットマスクを2進数で表してORを取る
    • OR を とった結果が サブネットマスクと異なるのでネットワークアドレスを表していない
ip          : 11000000.00000000.00000010.00000001
subnet      : 11111111.11111111.11111111.00000000
ip OR subnet: 11111111.11111111.11111111.00000001

コード

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class Main {

        public static void main(String[] args) throws Exception {

                // "192.0.2.0/24" のような形式を受け取ったとする
                String cidr = args[0];

                // IP とCIDRブロック に分離
                String ipAddress = cidr.split("/")[0];
                String ciderBlock = cidr.split("/")[1];
                System.out.println("ipAddress: " + ipAddress + " ciderBlock:" + ciderBlock);

                // CIDRブロックをサブネットマスクに変換
                // サブネットマスクを数値に変換
                long subnetMaskLong = getSubnetMask(ciderBlock);
                String subnetMask = long2IpAddress(subnetMaskLong);
                System.out.println("ciderBlock: " + ciderBlock + " -> subnetMask:" + subnetMask );
                System.out.println("subnetMask: " + subnetMask + " subnetMask(long):" + subnetMaskLong);

                // IpAddress を数値に変換
                long ipAddressLong = ipAddress2Long(ipAddress);
                System.out.println("ipAddress : " + ipAddress + " ipAddress(long):" +  ipAddressLong);

                // IP と サブネットマスク のORを取ってサブネットマスクが正しいか判定
                long ipAddressOrSubnetMask =  (subnetMaskLong | ipAddressLong);
                System.out.println("ip          : " +  long2BinaryIpAddress(ipAddressLong));
                System.out.println("subnet      : " +  long2BinaryIpAddress(subnetMaskLong));
                System.out.println("ip OR subnet: " + long2BinaryIpAddress(ipAddressOrSubnetMask));

                if (ipAddressOrSubnetMask == subnetMaskLong){
                        System.out.println("SUCCESS");
                }else{
                        System.out.println("!! ERROR !!");
                }

        }

        /**
         *  cidrBlock から サブネットマスクを特定する
         * @param cidrBlock 1~32
         * @return サブネットマスク
         */
        private static long getSubnetMask(String cidrBlock){
                return getSubnetMask(Integer.parseInt(cidrBlock));
        }

        /**
         *  cidrBlock から サブネットマスクを特定する
         * @param cidrBlock 1~32
         * @return サブネットマスク
         */
        private static long getSubnetMask(int cidrBlock){
                long bit1       = 0b0000_0000_0000_0000_0001;
                long subnetMask = 0b0000_0000_0000_0000_0000;
                int bitCount = 32;
                for (int i = cidrBlock; i > 0; i--) {
                        subnetMask |= (bit1 << bitCount-1);
                        bitCount--;
                }
                return subnetMask;
        }

        /**
         * long -> IpAddress
         * @param val 2進数 IpAddress (long)
         * @return IpAddress (文字列)
         */
        private static String long2IpAddress(long val){
                String longVal = Long.toBinaryString(val);
                List<String> eightCharacterDelimitedList = eightCharacterDelimited(longVal);

                return eightCharacterDelimitedList.stream()
                        .map(x -> Integer.parseInt(x,2))
                        .map(x -> Integer.toString(x))
                        .collect(Collectors.joining("."));
        }

        /**
         * long -> IpAddress
         * @param val 2進数 IpAddress (long)
         * @return 2進数 IpAddress (文字列)
         */
        private static String long2BinaryIpAddress(long val){
                String longVal = Long.toBinaryString(val);
                List<String> eightCharacterDelimitedList = eightCharacterDelimited(longVal);

                return String.join(".", eightCharacterDelimitedList);
        }

        /**
         * ipAddress -> long
         * @param ipAddress IpAddress (文字列)
         * @return 2進数 IpAddress (long)
         */
        private static long ipAddress2Long(String ipAddress){
                String[] split = ipAddress.split(Pattern.quote("."));
                String binary = Arrays.stream(split)
                        .map(Integer::parseInt)
                        .map(Integer::toBinaryString)
                        .map(x -> zeroPadded(x,8))
                        .collect(Collectors.joining());
                return Long.parseLong(binary, 2);
        }

        /**
         * val を maxLength まで先頭0埋めする
         * @param val 文字列
         * @param maxLength 最大長
         * @return 0埋めされた文字列
         */
        private static String zeroPadded(String val, int maxLength){
                StringBuilder result = new StringBuilder();
                for (int i = val.length(); i < maxLength; i++){
                        result.append("0");
                }
                result.append(val);
                return result.toString();
        }

        /**
         * val を8文字区切りにして Listで 返す
         * @param val 文字列
         * @return 8文字区切りにした List
         */
        private static List<String> eightCharacterDelimited(String val){
                Matcher m = Pattern.compile("[\\s\\S]{1,8}").matcher(val);
                List<String> separatedList = new ArrayList<>();
                while (m.find()) {
                        String group = m.group();
                        separatedList.add(group);
                }
                return separatedList;
        }
}