Skip to content

Commit

Permalink
8277608: Address IP Addressing
Browse files Browse the repository at this point in the history
Reviewed-by: dfuchs, rhalade, michaelm
  • Loading branch information
AlekseiEfimov authored and slowhog committed Jul 19, 2022
1 parent ec1d338 commit cdc1582
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 12 deletions.
4 changes: 2 additions & 2 deletions src/java.base/share/classes/java/net/HostPortrange.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -137,7 +137,7 @@ public int hashCode() {
}
this.ipv4 = this.literal = ipv4;
if (ipv4) {
byte[] ip = IPAddressUtil.textToNumericFormatV4(hoststr);
byte[] ip = IPAddressUtil.validateNumericFormatV4(hoststr);
if (ip == null) {
throw new IllegalArgumentException("illegal IPv4 address");
}
Expand Down
18 changes: 14 additions & 4 deletions src/java.base/share/classes/java/net/InetAddress.java
Expand Up @@ -1254,7 +1254,11 @@ private String removeComments(String hostsEntry) {
private byte [] createAddressByteArray(String addrStr) {
byte[] addrArray;
// check if IPV4 address - most likely
addrArray = IPAddressUtil.textToNumericFormatV4(addrStr);
try {
addrArray = IPAddressUtil.validateNumericFormatV4(addrStr);
} catch (IllegalArgumentException iae) {
return null;
}
if (addrArray == null) {
addrArray = IPAddressUtil.textToNumericFormatV6(addrStr);
}
Expand Down Expand Up @@ -1470,13 +1474,19 @@ public static InetAddress[] getAllByName(String host)
}

// if host is an IP address, we won't do further lookup
if (Character.digit(host.charAt(0), 16) != -1
if (IPAddressUtil.digit(host.charAt(0), 16) != -1
|| (host.charAt(0) == ':')) {
byte[] addr = null;
byte[] addr;
int numericZone = -1;
String ifname = null;
// see if it is IPv4 address
addr = IPAddressUtil.textToNumericFormatV4(host);
try {
addr = IPAddressUtil.validateNumericFormatV4(host);
} catch (IllegalArgumentException iae) {
var uhe = new UnknownHostException(host);
uhe.initCause(iae);
throw uhe;
}
if (addr == null) {
// This is supposed to be an IPv6 literal
// Check if a numeric or string zone id is present
Expand Down
4 changes: 2 additions & 2 deletions src/java.base/share/classes/java/net/SocketPermission.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -468,7 +468,7 @@ private void init(String host, int mask) {
if (!host.isEmpty()) {
// see if we are being initialized with an IP address.
char ch = host.charAt(0);
if (ch == ':' || Character.digit(ch, 16) != -1) {
if (ch == ':' || IPAddressUtil.digit(ch, 16) != -1) {
byte ip[] = IPAddressUtil.textToNumericFormatV4(host);
if (ip == null) {
ip = IPAddressUtil.textToNumericFormatV6(host);
Expand Down
274 changes: 270 additions & 4 deletions src/java.base/share/classes/sun/net/util/IPAddressUtil.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,14 +25,16 @@

package sun.net.util;

import java.io.IOException;
import sun.security.action.GetPropertyAction;

import java.io.UncheckedIOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.nio.CharBuffer;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
Expand Down Expand Up @@ -100,7 +102,7 @@ public static byte[] textToNumericFormatV4(String src)
tmpValue = 0;
newOctet = true;
} else {
int digit = Character.digit(c, 10);
int digit = digit(c, 10);
if (digit < 0) {
return null;
}
Expand All @@ -125,6 +127,29 @@ public static byte[] textToNumericFormatV4(String src)
return res;
}

/**
* Validates if input string is a valid IPv4 address literal.
* If the "jdk.net.allowAmbiguousIPAddressLiterals" system property is set
* to {@code false}, or is not set then validation of the address string is performed as follows:
* If string can't be parsed by following IETF IPv4 address string literals
* formatting style rules (default one), but can be parsed by following BSD formatting
* style rules, the IPv4 address string content is treated as ambiguous and
* {@code IllegalArgumentException} is thrown.
*
* @param src input string
* @return bytes array if string is a valid IPv4 address string
* @throws IllegalArgumentException if "jdk.net.allowAmbiguousIPAddressLiterals" SP is set to
* "false" and IPv4 address string {@code "src"} is ambiguous
*/
public static byte[] validateNumericFormatV4(String src) {
byte[] parsedBytes = textToNumericFormatV4(src);
if (!ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE
&& parsedBytes == null && isBsdParsableV4(src)) {
throw new IllegalArgumentException("Invalid IP address literal: " + src);
}
return parsedBytes;
}

/*
* Convert IPv6 presentation level address to network order binary form.
* credit:
Expand Down Expand Up @@ -170,7 +195,7 @@ public static byte[] textToNumericFormatV6(String src)
val = 0;
while (i < srcb_length) {
ch = srcb[i++];
int chval = Character.digit(ch, 16);
int chval = digit(ch, 16);
if (chval != -1) {
val <<= 4;
val |= chval;
Expand Down Expand Up @@ -551,4 +576,245 @@ public static String checkHostString(String host) {
}
return null;
}

/**
* Returns the numeric value of the character {@code ch} in the
* specified radix.
*
* @param ch the character to be converted.
* @param radix the radix.
* @return the numeric value represented by the character in the
* specified radix.
*/
public static int digit(char ch, int radix) {
if (ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE) {
return Character.digit(ch, radix);
} else {
return parseAsciiDigit(ch, radix);
}
}

/**
* Try to parse String as IPv4 address literal by following
* BSD-style formatting rules.
*
* @param input input string
* @return {@code true} if input string is parsable as IPv4 address literal,
* {@code false} otherwise.
*/
public static boolean isBsdParsableV4(String input) {
char firstSymbol = input.charAt(0);
// Check if first digit is not a decimal digit
if (parseAsciiDigit(firstSymbol, DECIMAL) == -1) {
return false;
}

// Last character is dot OR is not a supported digit: [0-9,A-F,a-f]
char lastSymbol = input.charAt(input.length() - 1);
if (lastSymbol == '.' || parseAsciiHexDigit(lastSymbol) == -1) {
return false;
}

// Parse IP address fields
CharBuffer charBuffer = CharBuffer.wrap(input);
int fieldNumber = 0;
while (charBuffer.hasRemaining()) {
long fieldValue = -1L;
// Try to parse fields in all supported radixes
for (int radix : SUPPORTED_RADIXES) {
fieldValue = parseV4FieldBsd(radix, charBuffer, fieldNumber);
if (fieldValue >= 0) {
fieldNumber++;
break;
} else if (fieldValue == TERMINAL_PARSE_ERROR) {
return false;
}
}
// If field can't be parsed as one of supported radixes stop
// parsing
if (fieldValue < 0) {
return false;
}
}
return true;
}

/**
* Method tries to parse IP address field that starts from {@linkplain CharBuffer#position()
* current position} of the provided character buffer.
* <p>
* This method supports three {@code "radix"} values to decode field values in
* {@code "HEXADECIMAL (radix=16)"}, {@code "DECIMAL (radix=10)"} and
* {@code "OCTAL (radix=8)"} radixes.
* <p>
* If {@code -1} value is returned the char buffer position is reset to the value
* it was before it was called.
* <p>
* Method returns {@code -2} if formatting illegal for all supported {@code radix}
* values is observed, and there is no point in checking other radix values.
* That includes the following cases:<ul>
* <li>Two subsequent dots are observer
* <li>Number of dots more than 3
* <li>Field value exceeds max allowed
* <li>Character is not a valid digit for the requested {@code radix} value, given
* that a field has the radix specific prefix
* </ul>
*
* @param radix digits encoding radix to use for parsing. Valid values: 8, 10, 16.
* @param buffer {@code CharBuffer} with position set to the field's fist character
* @param fieldNumber parsed field number
* @return {@code CANT_PARSE_IN_RADIX} if field can not be parsed in requested {@code radix}.
* {@code TERMINAL_PARSE_ERROR} if field can't be parsed and the whole parse process should be terminated.
* Parsed field value otherwise.
*/
private static long parseV4FieldBsd(int radix, CharBuffer buffer, int fieldNumber) {
int initialPos = buffer.position();
long val = 0;
int digitsCount = 0;
if (!checkPrefix(buffer, radix)) {
val = CANT_PARSE_IN_RADIX;
}
boolean dotSeen = false;
while (buffer.hasRemaining() && val != CANT_PARSE_IN_RADIX && !dotSeen) {
char c = buffer.get();
if (c == '.') {
dotSeen = true;
// Fail if 4 dots in IP address string.
// fieldNumber counter starts from 0, therefore 3
if (fieldNumber == 3) {
// Terminal state, can stop parsing: too many fields
return TERMINAL_PARSE_ERROR;
}
// Check for literals with two dots, like '1.2..3', '1.2.3..'
if (digitsCount == 0) {
// Terminal state, can stop parsing: dot with no digits
return TERMINAL_PARSE_ERROR;
}
if (val > 255) {
// Terminal state, can stop parsing: too big value for an octet
return TERMINAL_PARSE_ERROR;
}
} else {
int dv = parseAsciiDigit(c, radix);
if (dv >= 0) {
digitsCount++;
val *= radix;
val += dv;
} else {
// Spotted digit can't be parsed in the requested 'radix'.
// The order in which radixes are checked - hex, octal, decimal:
// - if symbol is not a valid digit in hex radix - terminal
// - if symbol is not a valid digit in octal radix, and given
// that octal prefix was observed before - terminal
// - if symbol is not a valid digit in decimal radix - terminal
return TERMINAL_PARSE_ERROR;
}
}
}
if (val == CANT_PARSE_IN_RADIX) {
buffer.position(initialPos);
} else if (!dotSeen) {
// It is the last field - check its value
// This check will ensure that address strings with less
// than 4 fields, i.e. A, A.B and A.B.C address types
// contain value less then the allowed maximum for the last field.
long maxValue = (1L << ((4 - fieldNumber) * 8)) - 1;
if (val > maxValue) {
// Terminal state, can stop parsing: last field value exceeds its
// allowed value
return TERMINAL_PARSE_ERROR;
}
}
return val;
}

// This method moves the position of the supplied CharBuffer by analysing the digit prefix
// symbols if any.
// The caller should reset the position when method returns false.
private static boolean checkPrefix(CharBuffer buffer, int radix) {
return switch (radix) {
case OCTAL -> isOctalFieldStart(buffer);
case DECIMAL -> isDecimalFieldStart(buffer);
case HEXADECIMAL -> isHexFieldStart(buffer);
default -> throw new AssertionError("Not supported radix");
};
}

// This method always moves the position of the supplied CharBuffer
// removing the octal prefix symbols '0'.
// The caller should reset the position when method returns false.
private static boolean isOctalFieldStart(CharBuffer cb) {
// .0<EOS> is not treated as octal field
if (cb.remaining() < 2) {
return false;
}

// Fetch two first characters
int position = cb.position();
char first = cb.get();
char second = cb.get();

// Return false if the first char is not octal prefix '0' or second is a
// field separator - parseV4FieldBsd will reset position to start of the field.
// '.0.' fields will be successfully parsed in decimal radix.
boolean isOctalPrefix = first == '0' && second != '.';

// If the prefix looks like octal - consume '0', otherwise 'false' is returned
// and caller will reset the buffer position.
if (isOctalPrefix) {
cb.position(position + 1);
}
return isOctalPrefix;
}

// This method doesn't move the position of the supplied CharBuffer
private static boolean isDecimalFieldStart(CharBuffer cb) {
return cb.hasRemaining();
}

// This method always moves the position of the supplied CharBuffer
// removing the hexadecimal prefix symbols '0x'.
// The caller should reset the position when method returns false.
private static boolean isHexFieldStart(CharBuffer cb) {
if (cb.remaining() < 2) {
return false;
}
char first = cb.get();
char second = cb.get();
return first == '0' && (second == 'x' || second == 'X');
}

// Parse ASCII digit in given radix
private static int parseAsciiDigit(char c, int radix) {
assert radix == OCTAL || radix == DECIMAL || radix == HEXADECIMAL;
if (radix == HEXADECIMAL) {
return parseAsciiHexDigit(c);
}
int val = c - '0';
return (val < 0 || val >= radix) ? -1 : val;
}

// Parse ASCII digit in hexadecimal radix
private static int parseAsciiHexDigit(char digit) {
char c = Character.toLowerCase(digit);
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
return parseAsciiDigit(c, DECIMAL);
}

// Supported radixes
private static final int HEXADECIMAL = 16;
private static final int DECIMAL = 10;
private static final int OCTAL = 8;
// Order in which field formats are exercised to parse one IP address textual field
private static final int[] SUPPORTED_RADIXES = new int[]{HEXADECIMAL, OCTAL, DECIMAL};

// BSD parser's return values
private final static long CANT_PARSE_IN_RADIX = -1L;
private final static long TERMINAL_PARSE_ERROR = -2L;

private static final String ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP = "jdk.net.allowAmbiguousIPAddressLiterals";
private static final boolean ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE = Boolean.valueOf(
GetPropertyAction.privilegedGetProperty(ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP, "false"));
}

0 comments on commit cdc1582

Please sign in to comment.