/* * Copied from the DnsJava project * * Copyright (c) 1998-2011, Brian Wellington. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package io.milton.dns; import io.milton.dns.record.ARecord; import io.milton.dns.record.Lookup; import io.milton.dns.record.PTRRecord; import io.milton.dns.record.Record; import io.milton.dns.record.ReverseMap; import io.milton.dns.record.Type; import java.net.*; /** * Routines dealing with IP addresses. Includes functions similar to those in * the java.net.InetAddress class. * * @author Brian Wellington */ public final class Address { public static final int IPv4 = 1; public static final int IPv6 = 2; private Address() { } private static byte[] parseV4(String s) { int numDigits; int currentOctet; byte[] values = new byte[4]; int currentValue; int length = s.length(); currentOctet = 0; currentValue = 0; numDigits = 0; for (int i = 0; i < length; i++) { char c = s.charAt(i); if (c >= '0' && c <= '9') { /* Can't have more than 3 digits per octet. */ if (numDigits == 3) { return null; } /* Octets shouldn't start with 0, unless they are 0. */ if (numDigits > 0 && currentValue == 0) { return null; } numDigits++; currentValue *= 10; currentValue += (c - '0'); /* 255 is the maximum value for an octet. */ if (currentValue > 255) { return null; } } else if (c == '.') { /* Can't have more than 3 dots. */ if (currentOctet == 3) { return null; } /* Two consecutive dots are bad. */ if (numDigits == 0) { return null; } values[currentOctet++] = (byte) currentValue; currentValue = 0; numDigits = 0; } else { return null; } } /* Must have 4 octets. */ if (currentOctet != 3) { return null; } /* The fourth octet can't be empty. */ if (numDigits == 0) { return null; } values[currentOctet] = (byte) currentValue; return values; } private static byte[] parseV6(String s) { int range = -1; byte[] data = new byte[16]; String[] tokens = s.split(":", -1); int first = 0; int last = tokens.length - 1; if (tokens[0].length() == 0) { // If the first two tokens are empty, it means the string // started with ::, which is fine. If only the first is // empty, the string started with :, which is bad. if (last - first > 0 && tokens[1].length() == 0) { first++; } else { return null; } } if (tokens[last].length() == 0) { // If the last two tokens are empty, it means the string // ended with ::, which is fine. If only the last is // empty, the string ended with :, which is bad. if (last - first > 0 && tokens[last - 1].length() == 0) { last--; } else { return null; } } if (last - first + 1 > 8) { return null; } int i, j; for (i = first, j = 0; i <= last; i++) { if (tokens[i].length() == 0) { if (range >= 0) { return null; } range = j; continue; } if (tokens[i].indexOf('.') >= 0) { // An IPv4 address must be the last component if (i < last) { return null; } // There can't have been more than 6 components. if (i > 6) { return null; } byte[] v4addr = Address.toByteArray(tokens[i], IPv4); if (v4addr == null) { return null; } for (int k = 0; k < 4; k++) { data[j++] = v4addr[k]; } break; } try { for (int k = 0; k < tokens[i].length(); k++) { char c = tokens[i].charAt(k); if (Character.digit(c, 16) < 0) { return null; } } int x = Integer.parseInt(tokens[i], 16); if (x > 0xFFFF || x < 0) { return null; } data[j++] = (byte) (x >>> 8); data[j++] = (byte) (x & 0xFF); } catch (NumberFormatException e) { return null; } } if (j < 16 && range < 0) { return null; } if (range >= 0) { int empty = 16 - j; System.arraycopy(data, range, data, range + empty, j - range); for (i = range; i < range + empty; i++) { data[i] = 0; } } return data; } /** * Convert a string containing an IP address to an array of 4 or 16 * integers. * * @param s The address, in text format. * @param family The address family. * @return The address */ public static int[] toArray(String s, int family) { byte[] byteArray = toByteArray(s, family); if (byteArray == null) { return null; } int[] intArray = new int[byteArray.length]; for (int i = 0; i < byteArray.length; i++) { intArray[i] = byteArray[i] & 0xFF; } return intArray; } /** * Convert a string containing an IPv4 address to an array of 4 integers. * * @param s The address, in text format. * @return The address */ public static int[] toArray(String s) { return toArray(s, IPv4); } /** * Convert a string containing an IP address to an array of 4 or 16 bytes. * * @param s The address, in text format. * @param family The address family. * @return The address */ public static byte[] toByteArray(String s, int family) { if (family == IPv4) { return parseV4(s); } else if (family == IPv6) { return parseV6(s); } else { throw new IllegalArgumentException("unknown address family"); } } /** * Determines if a string contains a valid IP address. * * @param s The string * @return Whether the string contains a valid IP address */ public static boolean isDottedQuad(String s) { byte[] address = Address.toByteArray(s, IPv4); return (address != null); } /** * Converts a byte array containing an IPv4 address into a dotted quad * string. * * @param addr The array * @return The string representation */ public static String toDottedQuad(byte[] addr) { return ((addr[0] & 0xFF) + "." + (addr[1] & 0xFF) + "." + (addr[2] & 0xFF) + "." + (addr[3] & 0xFF)); } /** * Converts an int array containing an IPv4 address into a dotted quad * string. * * @param addr The array * @return The string representation */ public static String toDottedQuad(int[] addr) { return (addr[0] + "." + addr[1] + "." + addr[2] + "." + addr[3]); } private static Record[] lookupHostName(String name) throws UnknownHostException { try { Record[] records = new Lookup(name).run(); if (records == null) { throw new UnknownHostException("unknown host"); } return records; } catch (TextParseException e) { throw new UnknownHostException("invalid name"); } } private static InetAddress addrFromRecord(String name, Record r) throws UnknownHostException { ARecord a = (ARecord) r; return InetAddress.getByAddress(name, a.getAddress().getAddress()); } /** * Determines the IP address of a host * * @param name The hostname to look up * @return The first matching IP address * @exception UnknownHostException The hostname does not have any addresses */ public static InetAddress getByName(String name) throws UnknownHostException { try { return getByAddress(name); } catch (UnknownHostException e) { Record[] records = lookupHostName(name); return addrFromRecord(name, records[0]); } } /** * Determines all IP address of a host * * @param name The hostname to look up * @return All matching IP addresses * @exception UnknownHostException The hostname does not have any addresses */ public static InetAddress[] getAllByName(String name) throws UnknownHostException { try { InetAddress addr = getByAddress(name); return new InetAddress[]{addr}; } catch (UnknownHostException e) { Record[] records = lookupHostName(name); InetAddress[] addrs = new InetAddress[records.length]; for (int i = 0; i < records.length; i++) { addrs[i] = addrFromRecord(name, records[i]); } return addrs; } } /** * Converts an address from its string representation to an IP address. The * address can be either IPv4 or IPv6. * * @param addr The address, in string form * @return The IP addresses * @exception UnknownHostException The address is not a valid IP address. */ public static InetAddress getByAddress(String addr) throws UnknownHostException { byte[] bytes; bytes = toByteArray(addr, IPv4); if (bytes != null) { return InetAddress.getByAddress(bytes); } bytes = toByteArray(addr, IPv6); if (bytes != null) { return InetAddress.getByAddress(bytes); } throw new UnknownHostException("Invalid address: " + addr); } /** * Converts an address from its string representation to an IP address in a * particular family. * * @param addr The address, in string form * @param family The address family, either IPv4 or IPv6. * @return The IP addresses * @exception UnknownHostException The address is not a valid IP address in * the specified address family. */ public static InetAddress getByAddress(String addr, int family) throws UnknownHostException { if (family != IPv4 && family != IPv6) { throw new IllegalArgumentException("unknown address family"); } byte[] bytes; bytes = toByteArray(addr, family); if (bytes != null) { return InetAddress.getByAddress(bytes); } throw new UnknownHostException("Invalid address: " + addr); } /** * Determines the hostname for an address * * @param addr The address to look up * @return The associated host name * @exception UnknownHostException There is no hostname for the address */ public static String getHostName(InetAddress addr) throws UnknownHostException { Name name = ReverseMap.fromAddress(addr); Record[] records = new Lookup(name, Type.PTR).run(); if (records == null) { throw new UnknownHostException("unknown address"); } PTRRecord ptr = (PTRRecord) records[0]; return ptr.getTarget().toString(); } /** * Returns the family of an InetAddress. * * @param address The supplied address. * @return The family, either IPv4 or IPv6. */ public static int familyOf(InetAddress address) { if (address instanceof Inet4Address) { return IPv4; } if (address instanceof Inet6Address) { return IPv6; } throw new IllegalArgumentException("unknown address family"); } /** * Returns the length of an address in a particular family. * * @param family The address family, either IPv4 or IPv6. * @return The length of addresses in that family. */ public static int addressLength(int family) { if (family == IPv4) { return 4; } if (family == IPv6) { return 16; } throw new IllegalArgumentException("unknown address family"); } /** * Truncates an address to the specified number of bits. For example, * truncating the address 10.1.2.3 to 8 bits would yield 10.0.0.0. * * @param address The source address * @param maskLength The number of bits to truncate the address to. */ public static InetAddress truncate(InetAddress address, int maskLength) { int family = familyOf(address); int maxMaskLength = addressLength(family) * 8; if (maskLength < 0 || maskLength > maxMaskLength) { throw new IllegalArgumentException("invalid mask length"); } if (maskLength == maxMaskLength) { return address; } byte[] bytes = address.getAddress(); for (int i = maskLength / 8 + 1; i < bytes.length; i++) { bytes[i] = 0; } int maskBits = maskLength % 8; int bitmask = 0; for (int i = 0; i < maskBits; i++) { bitmask |= (1 << (7 - i)); } bytes[maskLength / 8] &= bitmask; try { return InetAddress.getByAddress(bytes); } catch (UnknownHostException e) { throw new IllegalArgumentException("invalid address"); } } }