1 /*
   2  * Copyright (c) 2004, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.net.util;
  27 
  28 import java.io.IOException;
  29 import java.io.UncheckedIOException;
  30 import java.net.Inet6Address;
  31 import java.net.InetAddress;
  32 import java.net.InetSocketAddress;
  33 import java.net.NetworkInterface;
  34 import java.net.SocketException;
  35 import java.security.AccessController;
  36 import java.security.PrivilegedExceptionAction;
  37 import java.security.PrivilegedActionException;
  38 import java.util.List;
  39 import java.util.concurrent.ConcurrentHashMap;
  40 import java.util.stream.Collectors;
  41 
  42 public class IPAddressUtil {
  43     private static final int INADDR4SZ = 4;
  44     private static final int INADDR16SZ = 16;
  45     private static final int INT16SZ = 2;
  46 
  47     /*
  48      * Converts IPv4 address in its textual presentation form
  49      * into its numeric binary form.
  50      *
  51      * @param src a String representing an IPv4 address in standard format
  52      * @return a byte array representing the IPv4 numeric address
  53      */
  54     @SuppressWarnings("fallthrough")
  55     public static byte[] textToNumericFormatV4(String src)
  56     {
  57         byte[] res = new byte[INADDR4SZ];
  58 
  59         long tmpValue = 0;
  60         int currByte = 0;
  61         boolean newOctet = true;
  62 
  63         int len = src.length();
  64         if (len == 0 || len > 15) {
  65             return null;
  66         }
  67         /*
  68          * When only one part is given, the value is stored directly in
  69          * the network address without any byte rearrangement.
  70          *
  71          * When a two part address is supplied, the last part is
  72          * interpreted as a 24-bit quantity and placed in the right
  73          * most three bytes of the network address. This makes the
  74          * two part address format convenient for specifying Class A
  75          * network addresses as net.host.
  76          *
  77          * When a three part address is specified, the last part is
  78          * interpreted as a 16-bit quantity and placed in the right
  79          * most two bytes of the network address. This makes the
  80          * three part address format convenient for specifying
  81          * Class B net- work addresses as 128.net.host.
  82          *
  83          * When four parts are specified, each is interpreted as a
  84          * byte of data and assigned, from left to right, to the
  85          * four bytes of an IPv4 address.
  86          *
  87          * We determine and parse the leading parts, if any, as single
  88          * byte values in one pass directly into the resulting byte[],
  89          * then the remainder is treated as a 8-to-32-bit entity and
  90          * translated into the remaining bytes in the array.
  91          */
  92         for (int i = 0; i < len; i++) {
  93             char c = src.charAt(i);
  94             if (c == '.') {
  95                 if (newOctet || tmpValue < 0 || tmpValue > 0xff || currByte == 3) {
  96                     return null;
  97                 }
  98                 res[currByte++] = (byte) (tmpValue & 0xff);
  99                 tmpValue = 0;
 100                 newOctet = true;
 101             } else {
 102                 int digit = Character.digit(c, 10);
 103                 if (digit < 0) {
 104                     return null;
 105                 }
 106                 tmpValue *= 10;
 107                 tmpValue += digit;
 108                 newOctet = false;
 109             }
 110         }
 111         if (newOctet || tmpValue < 0 || tmpValue >= (1L << ((4 - currByte) * 8))) {
 112             return null;
 113         }
 114         switch (currByte) {
 115             case 0:
 116                 res[0] = (byte) ((tmpValue >> 24) & 0xff);
 117             case 1:
 118                 res[1] = (byte) ((tmpValue >> 16) & 0xff);
 119             case 2:
 120                 res[2] = (byte) ((tmpValue >>  8) & 0xff);
 121             case 3:
 122                 res[3] = (byte) ((tmpValue >>  0) & 0xff);
 123         }
 124         return res;
 125     }
 126 
 127     /*
 128      * Convert IPv6 presentation level address to network order binary form.
 129      * credit:
 130      *  Converted from C code from Solaris 8 (inet_pton)
 131      *
 132      * Any component of the string following a per-cent % is ignored.
 133      *
 134      * @param src a String representing an IPv6 address in textual format
 135      * @return a byte array representing the IPv6 numeric address
 136      */
 137     public static byte[] textToNumericFormatV6(String src)
 138     {
 139         // Shortest valid string is "::", hence at least 2 chars
 140         if (src.length() < 2) {
 141             return null;
 142         }
 143 
 144         int colonp;
 145         char ch;
 146         boolean saw_xdigit;
 147         int val;
 148         char[] srcb = src.toCharArray();
 149         byte[] dst = new byte[INADDR16SZ];
 150 
 151         int srcb_length = srcb.length;
 152         int pc = src.indexOf ('%');
 153         if (pc == srcb_length -1) {
 154             return null;
 155         }
 156 
 157         if (pc != -1) {
 158             srcb_length = pc;
 159         }
 160 
 161         colonp = -1;
 162         int i = 0, j = 0;
 163         /* Leading :: requires some special handling. */
 164         if (srcb[i] == ':')
 165             if (srcb[++i] != ':')
 166                 return null;
 167         int curtok = i;
 168         saw_xdigit = false;
 169         val = 0;
 170         while (i < srcb_length) {
 171             ch = srcb[i++];
 172             int chval = Character.digit(ch, 16);
 173             if (chval != -1) {
 174                 val <<= 4;
 175                 val |= chval;
 176                 if (val > 0xffff)
 177                     return null;
 178                 saw_xdigit = true;
 179                 continue;
 180             }
 181             if (ch == ':') {
 182                 curtok = i;
 183                 if (!saw_xdigit) {
 184                     if (colonp != -1)
 185                         return null;
 186                     colonp = j;
 187                     continue;
 188                 } else if (i == srcb_length) {
 189                     return null;
 190                 }
 191                 if (j + INT16SZ > INADDR16SZ)
 192                     return null;
 193                 dst[j++] = (byte) ((val >> 8) & 0xff);
 194                 dst[j++] = (byte) (val & 0xff);
 195                 saw_xdigit = false;
 196                 val = 0;
 197                 continue;
 198             }
 199             if (ch == '.' && ((j + INADDR4SZ) <= INADDR16SZ)) {
 200                 String ia4 = src.substring(curtok, srcb_length);
 201                 /* check this IPv4 address has 3 dots, i.e. A.B.C.D */
 202                 int dot_count = 0, index=0;
 203                 while ((index = ia4.indexOf ('.', index)) != -1) {
 204                     dot_count ++;
 205                     index ++;
 206                 }
 207                 if (dot_count != 3) {
 208                     return null;
 209                 }
 210                 byte[] v4addr = textToNumericFormatV4(ia4);
 211                 if (v4addr == null) {
 212                     return null;
 213                 }
 214                 for (int k = 0; k < INADDR4SZ; k++) {
 215                     dst[j++] = v4addr[k];
 216                 }
 217                 saw_xdigit = false;
 218                 break;  /* '\0' was seen by inet_pton4(). */
 219             }
 220             return null;
 221         }
 222         if (saw_xdigit) {
 223             if (j + INT16SZ > INADDR16SZ)
 224                 return null;
 225             dst[j++] = (byte) ((val >> 8) & 0xff);
 226             dst[j++] = (byte) (val & 0xff);
 227         }
 228 
 229         if (colonp != -1) {
 230             int n = j - colonp;
 231 
 232             if (j == INADDR16SZ)
 233                 return null;
 234             for (i = 1; i <= n; i++) {
 235                 dst[INADDR16SZ - i] = dst[colonp + n - i];
 236                 dst[colonp + n - i] = 0;
 237             }
 238             j = INADDR16SZ;
 239         }
 240         if (j != INADDR16SZ)
 241             return null;
 242         byte[] newdst = convertFromIPv4MappedAddress(dst);
 243         if (newdst != null) {
 244             return newdst;
 245         } else {
 246             return dst;
 247         }
 248     }
 249 
 250     /**
 251      * @param src a String representing an IPv4 address in textual format
 252      * @return a boolean indicating whether src is an IPv4 literal address
 253      */
 254     public static boolean isIPv4LiteralAddress(String src) {
 255         return textToNumericFormatV4(src) != null;
 256     }
 257 
 258     /**
 259      * @param src a String representing an IPv6 address in textual format
 260      * @return a boolean indicating whether src is an IPv6 literal address
 261      */
 262     public static boolean isIPv6LiteralAddress(String src) {
 263         return textToNumericFormatV6(src) != null;
 264     }
 265 
 266     /*
 267      * Convert IPv4-Mapped address to IPv4 address. Both input and
 268      * returned value are in network order binary form.
 269      *
 270      * @param src a String representing an IPv4-Mapped address in textual format
 271      * @return a byte array representing the IPv4 numeric address
 272      */
 273     public static byte[] convertFromIPv4MappedAddress(byte[] addr) {
 274         if (isIPv4MappedAddress(addr)) {
 275             byte[] newAddr = new byte[INADDR4SZ];
 276             System.arraycopy(addr, 12, newAddr, 0, INADDR4SZ);
 277             return newAddr;
 278         }
 279         return null;
 280     }
 281 
 282     /**
 283      * Utility routine to check if the InetAddress is an
 284      * IPv4 mapped IPv6 address.
 285      *
 286      * @return a <code>boolean</code> indicating if the InetAddress is
 287      * an IPv4 mapped IPv6 address; or false if address is IPv4 address.
 288      */
 289     private static boolean isIPv4MappedAddress(byte[] addr) {
 290         if (addr.length < INADDR16SZ) {
 291             return false;
 292         }
 293         if ((addr[0] == 0x00) && (addr[1] == 0x00) &&
 294             (addr[2] == 0x00) && (addr[3] == 0x00) &&
 295             (addr[4] == 0x00) && (addr[5] == 0x00) &&
 296             (addr[6] == 0x00) && (addr[7] == 0x00) &&
 297             (addr[8] == 0x00) && (addr[9] == 0x00) &&
 298             (addr[10] == (byte)0xff) &&
 299             (addr[11] == (byte)0xff))  {
 300             return true;
 301         }
 302         return false;
 303     }
 304     /**
 305      * Mapping from unscoped local Inet(6)Address to the same address
 306      * including the correct scope-id, determined from NetworkInterface.
 307      */
 308     private final static ConcurrentHashMap<InetAddress,InetAddress>
 309         cache = new ConcurrentHashMap<>();
 310 
 311     /**
 312      * Returns a scoped version of the supplied local, link-local ipv6 address
 313      * if that scope-id can be determined from local NetworkInterfaces.
 314      * If the address already has a scope-id or if the address is not local, ipv6
 315      * or link local, then the original address is returned.
 316      *
 317      * @param addr
 318      * @exception SocketException if the given ipv6 link local address is found
 319      *            on more than one local interface
 320      * @return
 321      */
 322     public static InetAddress toScopedAddress(InetAddress address)
 323         throws SocketException {
 324 
 325         if (address instanceof Inet6Address && address.isLinkLocalAddress()
 326             && ((Inet6Address) address).getScopeId() == 0) {
 327 
 328             InetAddress cached = null;
 329             try {
 330                 cached = cache.computeIfAbsent(address, k -> findScopedAddress(k));
 331             } catch (UncheckedIOException e) {
 332                 throw (SocketException)e.getCause();
 333             }
 334             return cached != null ? cached : address;
 335         } else {
 336             return address;
 337         }
 338     }
 339 
 340     /**
 341      * Same as above for InetSocketAddress
 342      */
 343     public static InetSocketAddress toScopedAddress(InetSocketAddress address)
 344         throws SocketException {
 345         InetAddress addr;
 346         InetAddress orig = address.getAddress();
 347         if ((addr = toScopedAddress(orig)) == orig) {
 348             return address;
 349         } else {
 350             return new InetSocketAddress(addr, address.getPort());
 351         }
 352     }
 353 
 354     private static InetAddress findScopedAddress(InetAddress address) {
 355         PrivilegedExceptionAction<List<InetAddress>> pa = () -> NetworkInterface.networkInterfaces()
 356                 .flatMap(NetworkInterface::inetAddresses)
 357                 .filter(a -> (a instanceof Inet6Address)
 358                         && address.equals(a)
 359                         && ((Inet6Address) a).getScopeId() != 0)
 360                 .collect(Collectors.toList());
 361         List<InetAddress> result;
 362         try {
 363             result = AccessController.doPrivileged(pa);
 364             var sz = result.size();
 365             if (sz == 0)
 366                 return null;
 367             if (sz > 1)
 368                 throw new UncheckedIOException(new SocketException(
 369                     "Duplicate link local addresses: must specify scope-id"));
 370             return result.get(0);
 371         } catch (PrivilegedActionException pae) {
 372             return null;
 373         }
 374     }
 375 }