Javaで文字列がネットワークを表すCIDR形式として正しいか判定する
この記事は最終更新日から6年以上が経過しています。
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 をチェックする
- IPアドレス と ciderBlock に分解する
- ipAddress: 192.0.2.0
- ciderBlock:24
- ciderBlock をサブネットマスクに変換する
- ciderBlock: 24 -> subnetMask:255.255.255.0
- 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 をチェックする
- IPアドレス と ciderBlock に分解する
- ipAddress: 192.0.2.1
- ciderBlock:24
- ciderBlock をサブネットマスクに変換する
- ciderBlock: 24 -> subnetMask:255.255.255.0
- 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;
}
}