// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) package org.xbill.DNS; import java.net.*; import java.net.Inet6Address; /** * 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(addr, bytes); bytes = toByteArray(addr, IPv6); if (bytes != null) return InetAddress.getByAddress(addr, 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(addr, 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"); } } }