文字列がIPアドレスであるかどうかの判定は正規表現に基づいて実装されることが多く、外部依存パッケージを導入する場合でも、自分で正規の実装を記述する場合でも、基本的には正規表現による判定となります。ただし、例外は、jdk 自体がInet4Address.getByName
IP アドレスの判断を実現するのに役立つメソッドを提供していることです。この記事では、文字列が IPV4 アドレスであるか IPV6 アドレスであるかを判断する一般的な方法を詳細にリストし、その制限を分析します。
1. IPV4アドレスかIPV6アドレスかを判断する一般的な方法
1. Apache Commons Validator を使用して判断する
依存関係パッケージをインポートする必要がある
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.6</version>
</dependency>
依存関係パッケージを使用すると、InetAddressValidator
後続の呼び出しのコア API は問題なく動作します。
1.1 IPV4 アドレスかどうかを確認する
private static final InetAddressValidator VALIDATOR = InetAddressValidator.getInstance();
public static boolean isValidIPV4ByValidator(String inetAddress) {
return VALIDATOR.isValidInet4Address(inetAddress);
}
1.2 IPV6 アドレスかどうかを確認する
public static boolean isValidIPV6ByValidator(String inetAddress) {
return VALIDATOR.isValidInet6Address(inetAddress);
}
1.3 アドレスが IPV6 か IPV4 かを判断する
public static boolean isValidIPV6ByValidator(String inetAddress) {
return VALIDATOR.isValid(inetAddress);
}
2. グアバを使って判断する
依存パッケージをインポートする
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
</dependency>
を呼び出すことInetAddresses.isInetAddress
で迅速な判定を実現できますが、このメソッドでは文字列がIPV4アドレスかIPV6アドレスかを同時に判定してしまうため、どちらか一方の形式のみを判定したい場合には機能しません。
public static boolean isValidByGuava(String ip) {
return InetAddresses.isInetAddress(ip);
}
3. OWASP 正規表現を使用して判断する
OWASP は、一般的な Web アプリケーション用語を検証するために使用される一連の正規表現を提供しており、それらはOWASP_Validation_Regex_Repositoryを通じて取得できます。この判定方法ではIPV4アドレスかどうかのみ判定できます。
private static final String OWASP_IPV4_REGEX =
"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
private static final Pattern OWASP_IPv4_PATTERN = Pattern.compile(OWASP_IPV4_REGEX);
public static boolean isValidIPV4ByOWASP(String ip) {
if (ip == null || ip.trim().isEmpty()) {
return false;
}
return OWASP_IPv4_PATTERN.matcher(ip).matches();
}
4. カスタム正規表現を使用して判断する
以下のように、カスタム正規表現を使用して文字列が IPV4 アドレスであるかどうかを判断します。その正規表現と実装の詳細は、実際には最初の解決策の IPV4 の判断と一致しています。文字列が IPV4 アドレスであるかどうかだけを判断したい場合は、外部パッケージを導入するのが面倒な場合は、3 と 4 の 2 つの方法が適しています。
private static final String IPV4_REGEX =
"^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$";
private static final Pattern IPv4_PATTERN = Pattern.compile(IPV4_REGEX);
public static boolean isValidIPV4ByCustomRegex(String ip) {
if (ip == null || ip.trim().isEmpty()) {
return false;
}
if (!IPv4_PATTERN.matcher(ip).matches()) {
return false;
}
String[] parts = ip.split("\\.");
try {
for (String segment : parts) {
if (Integer.parseInt(segment) > 255 ||
(segment.length() > 1 && segment.startsWith("0"))) {
return false;
}
}
} catch (NumberFormatException e) {
return false;
}
return true;
}
5. JDK の組み込み Inet4Address を使用して判断する
Inet4Address
JDKはバージョン1.4以降、 IPに関する様々な検証操作を実装するクラスを提供しており、このクラスのgetByName
sumメソッドを組み合わせることでIPアドレス判定を実現できますが、これら2つのメソッドを頻繁に呼び出すとパフォーマンス上の問題が発生します。getHostAddress
JDK を使用して文字列が IPV4 アドレスであるかどうかを判断する方法は次のとおりです。
public static boolean isValidIPV4ByJDK(String ip) {
try {
return Inet4Address.getByName(ip)
.getHostAddress().equals(ip);
} catch (UnknownHostException ex) {
return false;
}
}
次に、ping コマンドには適していません。
1. IPV4の標準フォーマット
この記事で挙げたいくつかの判定方法は、標準的な IP アドレスを対象としています。標準とは、IP アドレスがカンマで区切られた 4 桁の 8 ビットの数値列で構成されていることを意味します。各桁は 8 ビットの長さしかないため、各桁の値は次のとおりです。 0 ~ 255 の範囲にする必要があります。関連文書はRFC5321を参照してください。
2. 正当性の検証
いくつかの文字列グループを選択します。一部の数字は欠落しており、一部の数値は 0 で始まり、一部は標準形式です。次に、上記の方法で有効な IP アドレスであるかどうかを判断します。
テスト プロセスについては詳細には説明しませんが、テスト ケースとテスト結果は次の表に直接まとめられています。
例 | isValidIPV4ByValidator | isValidIPV6ByValidator | グアバにより有効です | isValidIPV4ByOWASP | isValidIPV4ByCustomRegex | isValidIPV4ByJDK |
---|---|---|---|---|---|---|
172.8.9.28 | 真実 | 間違い | 真実 | 真実 | 真実 | 真実 |
192.168.0.072 | 間違い | 間違い | 間違い | 真実 | 間違い | 間違い |
172.08.9.28 | 間違い | 間違い | 間違い | 真実 | 間違い | 間違い |
172.9.28 | 間違い | 間違い | 間違い | 間違い | 間違い | 間違い |
192.168.072 | 間違い | 間違い | 間違い | 間違い | 間違い | 間違い |
192.168.1 | 間違い | 間違い | 間違い | 間違い | 間違い | 間違い |
2001:0db8:85a3:0000:0000:8a2e:0370:7334 | 間違い | 真実 | 真実 | 間違い | 間違い | 間違い |
これら 7 つのテスト ケースを通じて、次のことを確認するのは難しくありません。
- 最初の IP は正確に 4 ビットで、各ビットは 0 ~ 255 であり、0 で始まるビットはありません。IPV4 を判定するすべてのメソッドは、予想どおり true を返しました。
- 2番目と3番目のIPも4桁のアドレスですが、ある桁に0から始まる数字が出現しており、このときOWASP正規表現のメソッドはtrueを返し、その他のメソッドはfalseを返します。
- 4 番目、5 番目、および 6 番目の IP はすべて 3 桁のアドレスであり、すべてのメソッドは false を返します。
- 最後のは正当な ipv6 アドレスであり、
Apache Commons Validator
orパッケージGuava
が提供する判定メソッドにより通常は true を返すことができます。
3. 性能比較
この記事では、特に挙げた 5 番目の判定方法におけるパフォーマンスの問題について言及していますが、Inet4Address
判定 IP アドレスを使用するとどの程度のパフォーマンスが低下するのでしょうか? 大規模な不正な IP アドレスが入力として使用されていると判断された場合、この方法のパフォーマンス損失は想像を絶することが実験により証明されています。
この結論は、以下のテストによって検証されます。
private static List<String> generateFakeIp(int capacity) {
List<String> ipList = new ArrayList<String>(capacity);
for (int i = 0; i < capacity; i++) {
int parts = boundRandom(1, 3);
if (chanceOf50()) {
//each ip has 50% chance to be 4 parts
parts = 4;
}
StringBuilder sbBuilder = new StringBuilder();
for (int j = 0; j < parts; j++) {
if (sbBuilder.length() > 0) {
sbBuilder.append(".");
}
StringBuilder stringBuilder = new StringBuilder();
if (chanceOf10()) {
//each part has 10% chance to generate a fake number
stringBuilder.append('a');
} else {
//each part has 90% chance to generate the correct number
stringBuilder.append(boundRandom(0, 255));
}
sbBuilder.append(stringBuilder);
}
ipList.add(sbBuilder.toString());
}
return ipList;
}
private static long correctCount(List<String> ipList) {
return ipList.stream().filter(ip -> isValidIPV4ByCustomRegex(ip)).collect(Collectors.toList()).size();
}
// 50% chance
private static boolean chanceOf50() {
return boundRandom(0, 9) < 5;
}
// 10% chance
private static boolean chanceOf10() {
return boundRandom(0, 9) < 1;
}
private static Random random = new Random();
// random int between [start, end], both start and end are included
private static int boundRandom(int start, int end) {
return start + random.nextInt(end);
}
上記の方法を使用してgenerateFakeIp
、ランダムな IP アドレスのバッチを生成します。その一部は正しい形式であり、一部は不正な形式です。
主なテスト方法は次のとおりであり、これら 2 つの方法の IP アドレスの判定にかかる合計時間を比較して、isValidIPV4ByCustomRegex
パフォーマンスisValidIPV4ByJDK
の問題を分析します。
public static void performanceTest() {
List<String> ipList = generateFakeIp(100);
double chance = correctCount(ipList);
System.out.println("start testing, correct ip count is : " + chance);
long t1 = System.currentTimeMillis();
ipList.stream().forEach( ip-> isValidIPV4ByCustomRegex(ip));
long t2 = System.currentTimeMillis();
ipList.stream().forEach( ip-> isValidIPV4ByJDK(ip));
long t3 = System.currentTimeMillis();
System.out.println("isValidIPV4ByCustomRegex cost time : " + (t2-t1));
System.out.println("isValidIPV4ByJDK cost time : " + (t3-t2));
}
直接実行した後、次の結果を出力します。
start testing, correct ip count is : 37.0
isValidIPV4ByCustomRegex cost time : 2
isValidIPV4ByJDK cost time : 13745
100 個の IP のうち 37 個だけが正当な IP である場合、正規表現に基づく判定方法では 2 ミリ秒しかかからないのに対し、JDK の組み込み実装に基づく判定方法では 13 秒かかり、桁違いであることがわかります。Inet4Address
.アップ。テストベースを拡大するとさらに想像を絶することになりますので、実際の業務ではInet4Address
知財の合法性の判断には絶対に使用しないでください。
4. IPV4の判定方法がpingコマンドに適していない
標準 IPV4 形式のアドレスの場合は上記の判定方法で問題ありませんが、非標準 IPV4 形式のアドレスの一部は ping コマンドで正常に解析できます。
ping コマンドの場合、ここにリストされている 2 番目から 6 番目の IP アドレスは正当であり、正常に解析できます。
確認したいことがあります:
入力した IP アドレスの特定の桁が 0 で始まる場合も、正常に解析できることがわかります。写真から、解析できていることがわかります192.168.0.072
。。どうしてこれなの?192.168.0.58
172.08.9.28
172.08.9.28
ping コマンドで受信した IP アドレスに 0 で始まる数字がある場合、ping コマンドはその数字を 8 進数で解析しようとします。8 進数の 072 は 10 進数の 58 に対応するため、解析され192.168.0.072
ます192.168.0.58
。
172.08.9.28
0 で始まる数字が 8 進数形式に準拠していない場合でも、数字は 10 進数として扱われ、最上位の 0 は無視されます。 は有効な8 進数ではないため、10 進数として扱われ、最上位の桁は無視されます08
。 0、実際に解析されるのは172.8.9.28
また、入力 IP アドレスがカンマ区切りの 4 桁でない場合でも、ping コマンドは正常に解析できます。ping 196.168.072
、192.168
、を実際に、196
に解析すると、IP が 4 桁未満の場合、 ping コマンドは適切な位置に 0 を埋め込み、その規則は次のとおりであることがわかります。196.168.0.072
196.0.0.168
0.0.0.192
1 part (ping A) : 0.0.0.A
2 parts (ping A.B) : A.0.0.B
3 parts (ping A.B.C) : A.B.0.C
4 parts (ping A.B.C.D) : A.B.C.D
3. まとめ
文字列が IPV4 アドレスであるか IPV6 アドレスであるかを判断するこれらの方法には、同様の内部実装原則がありますが、最後の解決策を除き、すべて正規表現を使用して実装されます。
ただし、正規表現に基づく方法では 10 進数以外の数字をうまく処理できず、ping コマンドが受け取ることができる文字列はこれよりもはるかに複雑になります。は合法的な IP であるため、ping コマンドの基礎となるソース コードを理解しない限り、青天井に到達するのは難しいはずです。
もちろん、実際のビジネスシーンでは、文字列が正規の IP アドレスであるかどうかを標準形式に基づいて判断することが多いですが、この記事で紹介した信頼性の低い方法は気にせず、3 番目と最後の解決策を除いて、大胆に使用してください。