net.spfbl.whois.SubnetIPv6.java Source code

Java tutorial

Introduction

Here is the source code for net.spfbl.whois.SubnetIPv6.java

Source

/*
 * This file is part of SPFBL.
 * 
 * SPFBL is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * SPFBL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with SPFBL.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.spfbl.whois;

import net.spfbl.core.Server;
import net.spfbl.core.ProcessException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.Arrays;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import org.apache.commons.lang3.SerializationUtils;

/**
 * Representa uma Subnet de IPv6.
 * 
 * <h2>Mecanismo de busca</h2>
 * A busca de um bloco AS  realizada atravs de um mapa ordenado em rvore,
 * onde a chave  o primeiro endereo IP do bloco, 
 * convetido em inteiro de 64 bits, e o valor  o bloco propriamente dito.
 * O endereo IP da consulta  convertido em inteiro de 64 bits e localiza-se 
 * o endereo no mapa imediatamente inferior ou igual ao endereo do IP. 
 * Por conta do Java no trabalhar com unsigned int, 
 * a busca  feita de forma circular, ou seja, 
 * se no retornar na primeira busca, o ltimo registro do mapa  retornado.
 * Se algum bloco for encontrado, 
 *  feito um teste se o endereo do IP est contido no bloco encontrado.
 * Se entiver dentro, o bloco encontrado  considerado.
 * A busca consome o tempo de O(log2(n)).
 * 
 * @author Leandro Carlos Rodrigues <leandro@spfbl.net>
 */
public final class SubnetIPv6 extends Subnet {

    private static final long serialVersionUID = 1L;

    private final long address; // Primeiro endereo do bloco, primeiros 64 bits.
    private final long mask; // Mscara da subrede, primeiros 64 bits.

    /**
     * Construtor do blocos de pases.
     * @param inetnum o endereamento CIDR do bloco.
     * @param server o server que possui as informaes daquele bloco.
     */
    protected SubnetIPv6(String inetnum, String server) {
        super(inetnum, server);
        // Endereamento do bloco.
        this.mask = getMaskNet(inetnum);
        this.address = getAddressNet(inetnum) & mask; // utiliza a mscara para garantir que o endereo passado seja o primeiro endereo do bloco.
    }

    /**
     * Retorna o primeiro endereo do bloco em inteiro de 64 bits.
     * @return o primeiro endereo do bloco em inteiro de 64 bits.
     */
    public long getFirstAddress() {
        return address;
    }

    /**
     * Retorna o ltimo endereo do bloco em inteiro de 64 bits.
     * @return o ltimo endereo do bloco em inteiro de 64 bits.
     */
    public long getLastAddress() {
        return address | ~mask;
    }

    /**
     * Construtor do blocos de alocados para ASs.
     * @param result o resultado WHOIS do bloco.
     * @throws QueryException se houver alguma falha da atualizao do registro.
     */
    private SubnetIPv6(String result) throws ProcessException {
        super(result);
        // Endereamento do bloco.
        this.mask = getMaskNet(getInetnum());
        this.address = getAddressNet(getInetnum()) & mask; // utiliza a mscara para garantir que o endereo passado seja o primeiro endereo do bloco.
    }

    @Override
    protected boolean refresh() throws ProcessException {
        boolean isInetnum = super.refresh();
        // Atualiza flag de atualizao.
        CHANGED = true;
        return isInetnum;
    }

    /**
     * Retorna o endereo IP em inteiro de 64 bits da notao CIDR.
     * @param inetnum endereo de bloco em notao CIDR.
     * @return o endereo IP em inteiro de 64 bits da notao CIDR.
     */
    private static long getAddressNet(String inetnum) {
        int index = inetnum.indexOf('/');
        String ip = inetnum.substring(0, index);
        return getAddressIP(ip);
    }

    /**
     * Divide o endereo IPv6 em 16 bytes,
     * cada um na ordem correta dos blocos.
     * @param ip o endereo IPv6.
     * @return um vetor de 16 bytes representando o endereo IPv6.
     */
    public static byte[] splitByte(String ip) {
        byte[] address = new byte[16];
        int k = 0;
        for (short block : split(ip)) {
            address[k++] |= block >>> 8;
            address[k++] |= block;
        }
        return address;
    }

    public static short[] split(String ip, short[] mask) {
        short[] address = split(ip);
        for (int i = 0; i < 8; i++) {
            address[i] &= mask[i];
        }
        return address;
    }

    /**
     * Retorna a mcara IPv6 em 8 partes.
     * @param size o tamanho em bits da mscara que deve ser criada.
     * @return a mcara IPv6 em 8 partes.
     */
    public static short[] getMaskIPv6(String size) {
        return getMaskIPv6(Integer.parseInt(size));
    }

    /**
     * Retorna a mcara IPv6 em 8 partes.
     * @param size o tamanho em bits da mscara que deve ser criada.
     * @return a mcara IPv6 em 8 partes.
     */
    public static short[] getMaskIPv6(int size) {
        short[] mask = new short[8];
        int n = size / 16;
        int r = size % 16;
        int i;
        for (i = 0; i < n; i++) {
            mask[i] = (short) 0xFFFF;
        }
        if (i < mask.length && r > 0) {
            mask[i] = (short) (0xFFFF << 16 - r);
        }
        return mask;
    }

    public static String reverse(String ip) {
        String reverse = "";
        byte[] address = splitByte(ip);
        for (byte octeto : address) {
            String hexPart = Integer.toHexString((int) octeto & 0xFF);
            if (hexPart.length() == 1) {
                hexPart = "0" + hexPart;
            }
            for (char digit : hexPart.toCharArray()) {
                reverse = digit + "." + reverse;
            }
        }
        return reverse;
    }

    public static boolean isSLAAC(String ip) {
        if (SubnetIPv6.isValidIPv6(ip)) {
            byte[] byteArray = splitByte(ip);
            return (byteArray[11] & 0xFF) == 0xFF && (byteArray[12] & 0xFF) == 0xFE;
        } else {
            return false;
        }
    }

    public static String expandIPv6(String ip) {
        short[] splitedIP = split(ip);
        int p1 = splitedIP[0] & 0xFFFF;
        int p2 = splitedIP[1] & 0xFFFF;
        int p3 = splitedIP[2] & 0xFFFF;
        int p4 = splitedIP[3] & 0xFFFF;
        int p5 = splitedIP[4] & 0xFFFF;
        int p6 = splitedIP[5] & 0xFFFF;
        int p7 = splitedIP[6] & 0xFFFF;
        int p8 = splitedIP[7] & 0xFFFF;
        return String.format("%4s", Integer.toHexString(p1)).replace(' ', '0') + ":"
                + String.format("%4s", Integer.toHexString(p2)).replace(' ', '0') + ":"
                + String.format("%4s", Integer.toHexString(p3)).replace(' ', '0') + ":"
                + String.format("%4s", Integer.toHexString(p4)).replace(' ', '0') + ":"
                + String.format("%4s", Integer.toHexString(p5)).replace(' ', '0') + ":"
                + String.format("%4s", Integer.toHexString(p6)).replace(' ', '0') + ":"
                + String.format("%4s", Integer.toHexString(p7)).replace(' ', '0') + ":"
                + String.format("%4s", Integer.toHexString(p8)).replace(' ', '0');
    }

    public static String expandCIDRv6(String cidr) {
        int index = cidr.indexOf('/');
        String ip = cidr.substring(0, index);
        String mask = cidr.substring(index);
        ip = expandIPv6(ip);
        cidr = ip + mask;
        return cidr;
    }

    /**
     * Meio mais seguro de padronizar os endereos IP.
     * @param ip o endereo IPv6.
     * @return o endereo IPv6 padronizado.
     */
    public static String normalizeIPv6(String ip) {
        short[] splitedIP = split(ip);
        int p1 = splitedIP[0] & 0xFFFF;
        int p2 = splitedIP[1] & 0xFFFF;
        int p3 = splitedIP[2] & 0xFFFF;
        int p4 = splitedIP[3] & 0xFFFF;
        int p5 = splitedIP[4] & 0xFFFF;
        int p6 = splitedIP[5] & 0xFFFF;
        int p7 = splitedIP[6] & 0xFFFF;
        int p8 = splitedIP[7] & 0xFFFF;
        return Integer.toHexString(p1) + ":" + Integer.toHexString(p2) + ":" + Integer.toHexString(p3) + ":"
                + Integer.toHexString(p4) + ":" + Integer.toHexString(p5) + ":" + Integer.toHexString(p6) + ":"
                + Integer.toHexString(p7) + ":" + Integer.toHexString(p8);
    }

    /**
     * Divide o endereo IPv6 em 8 inteiros,
     * cada um na ordem correta dos blocos.
     * @param ip o endereo IPv6.
     * @return um vetor de 8 inteiros representando o endereo IPv6.
     */
    public static short[] split(String ip) {
        int k = 0;
        short[] address = new short[8];
        int beginIndex = 0;
        int endIndex;
        int count = 0;
        // Converte do inicio ao final.
        while ((endIndex = ip.indexOf(':', beginIndex)) != -1) {
            if (beginIndex == endIndex) {
                // Encontrou a abreviao central.
                break;
            } else {
                String block = ip.substring(beginIndex, endIndex);
                address[k++] |= Integer.valueOf(block, 16);
                beginIndex = endIndex + 1;
                count++;
            }
        }
        k = 7;
        count = 8 - count; // Calcula quantos blocos faltaram.
        endIndex = ip.length() - 1;
        // Converte invertido do final ao inicio.
        while (count-- > 0 && (beginIndex = ip.lastIndexOf(':', endIndex)) != -1) {
            if (beginIndex == endIndex) {
                // Encontrou a abreviao central.
                break;
            } else {
                String block = ip.substring(beginIndex + 1, endIndex + 1);
                address[k--] |= Integer.valueOf(block, 16);
                endIndex = beginIndex - 1;
            }
        }
        return address;
    }

    public static byte[] address(String ip) {
        byte[] address = new byte[16];
        short[] splitArray = split(ip);
        address[0] = (byte) (splitArray[0] >>> 8 & 0xFFFF);
        address[1] = (byte) (splitArray[0] & 0xFFFF);
        address[2] = (byte) (splitArray[1] >>> 8 & 0xFFFF);
        address[3] = (byte) (splitArray[1] & 0xFFFF);
        address[4] = (byte) (splitArray[2] >>> 8 & 0xFFFF);
        address[5] = (byte) (splitArray[2] & 0xFFFF);
        address[6] = (byte) (splitArray[3] >>> 8 & 0xFFFF);
        address[7] = (byte) (splitArray[3] & 0xFFFF);
        address[8] = (byte) (splitArray[4] >>> 8 & 0xFFFF);
        address[9] = (byte) (splitArray[4] & 0xFFFF);
        address[10] = (byte) (splitArray[5] >>> 8 & 0xFFFF);
        address[11] = (byte) (splitArray[5] & 0xFFFF);
        address[12] = (byte) (splitArray[6] >>> 8 & 0xFFFF);
        address[13] = (byte) (splitArray[6] & 0xFFFF);
        address[14] = (byte) (splitArray[7] >>> 8 & 0xFFFF);
        address[15] = (byte) (splitArray[7] & 0xFFFF);
        return address;
    }

    /**
     * Retorna o endereo IP em inteiro de 64 bits da notao IP.
     * Para fins de roteamento, os primeiros 64 so suficientes.
     * @param ip endereo de IP em notao IP.
     * @return o endereo IP em inteiro de 64 bits da notao IPv6.
     */
    protected static long getAddressIP(String ip) {
        long address = 0;
        short[] splitedAddress = split(ip);
        for (int i = 0; i < 4; i++) {
            address += (long) splitedAddress[i] & 0xFFFF;
            if (i < 3) {
                address <<= 16;
            }
        }
        return address;
    }

    /**
     * Retorna a mscara em inteiro de 64 bits da notao CIDR.
     * @param inetnum endereo de bloco em notao CIDR.
     * @return a mscara em inteiro de 64 bits da notao CIDR.
     */
    private static long getMaskNet(String inetnum) {
        int index = inetnum.indexOf('/');
        int mask = Integer.parseInt(inetnum.substring(index + 1));
        return 0xFFFFFFFFFFFFFFFFL << 64 - mask;
    }

    /**
     * Verifica se um IP  vlido na notao de IP.
     * @param ip o IP a ser verificado.
     * @return verdadeiro se um IP  vlido na notao de IPv6.
     */
    public static boolean isValidIPv6(String ip) {
        if (ip == null) {
            return false;
        } else {
            ip = ip.trim();
            ip = ip.toLowerCase();
            return Pattern.matches("^" + "([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + "([0-9a-fA-F]{1,4}:){1,7}:|"
                    + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"
                    + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"
                    + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"
                    + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"
                    + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"
                    + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|"
                    + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}" + "$", ip);
        }
    }

    public static boolean isReservedIPv6(String ip) {
        if (ip == null) {
            return false;
        } else if (SubnetIPv6.containsIP("0000::/8", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("0100::/8", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("0200::/7", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("0400::/6", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("0800::/5", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("1000::/4", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("2001::/32", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("2001:10::/28", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("2001:20::/28", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("2001:db8::/32", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("2002::/16", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("4000::/3", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("6000::/3", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("8000::/3", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("a000::/3", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("c000::/3", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("e000::/4", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("f000::/5", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("f800::/6", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("fc00::/7", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("fe00::/9", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("fe80::/10", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("fec0::/10", ip)) {
            return true;
        } else if (SubnetIPv6.containsIP("ff00::/8", ip)) {
            return true;
        } else {
            return false;
        }
    }

    private static BigInteger ADDRESS_MIN = new BigInteger("0");
    private static BigInteger ADDRESS_MAX = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16);
    private static BigInteger ADDRESS_UNIT = new BigInteger("1");
    private static BigInteger ADDRESS_OCTET = new BigInteger("FFFF", 16);

    public static String getNextIPv6(String ip) {
        if (ip == null) {
            return null;
        } else if (SubnetIPv6.isValidIPv6(ip)) {
            BigInteger address = new BigInteger(1, address(ip));
            if (address.equals(ADDRESS_MAX)) {
                return null;
            } else {
                address = address.add(ADDRESS_UNIT);
                int p8 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p7 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p6 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p5 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p4 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p3 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p2 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p1 = address.and(ADDRESS_OCTET).intValue();
                return Integer.toHexString(p1) + ":" + Integer.toHexString(p2) + ":" + Integer.toHexString(p3) + ":"
                        + Integer.toHexString(p4) + ":" + Integer.toHexString(p5) + ":" + Integer.toHexString(p6)
                        + ":" + Integer.toHexString(p7) + ":" + Integer.toHexString(p8);
            }
        } else {
            return null;
        }
    }

    public static String getPreviousIPv6(String ip) {
        if (ip == null) {
            return null;
        } else if (SubnetIPv6.isValidIPv6(ip)) {
            BigInteger address = new BigInteger(1, address(ip));
            if (address.equals(ADDRESS_MIN)) {
                return null;
            } else {
                address = address.subtract(ADDRESS_UNIT);
                int p8 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p7 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p6 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p5 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p4 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p3 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p2 = address.and(ADDRESS_OCTET).intValue();
                address = address.shiftRight(16);
                int p1 = address.and(ADDRESS_OCTET).intValue();
                return Integer.toHexString(p1) + ":" + Integer.toHexString(p2) + ":" + Integer.toHexString(p3) + ":"
                        + Integer.toHexString(p4) + ":" + Integer.toHexString(p5) + ":" + Integer.toHexString(p6)
                        + ":" + Integer.toHexString(p7) + ":" + Integer.toHexString(p8);
            }
        } else {
            return null;
        }
    }

    /**
     * Verifica se um CIDR  vlido na notao de IPv6.
     * @param cidr o CIDR a ser verificado.
     * @return verdadeiro se um CIDR  vlido na notao de IPv6.
     */
    public static boolean isValidCIDRv6(String cidr) {
        if (cidr == null) {
            return false;
        } else {
            cidr = cidr.trim();
            cidr = cidr.toLowerCase();
            return Pattern.matches("^" + "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + "([0-9a-fA-F]{1,4}:){1,7}:|"
                    + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"
                    + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"
                    + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"
                    + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"
                    + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"
                    + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|"
                    + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,})" + "/[0-9]{1,3}$", cidr);
        }
    }

    public static boolean isReverseIPv6(String reverse) {
        reverse = reverse.trim();
        reverse = reverse.toLowerCase();
        return Pattern.matches("^" + "(\\.?[a-f0-9]{1,4})" + "(\\.[a-f0-9]{1,4}){31}" + "$\\.?", reverse);
    }

    public static String reverseToIPv6(String reverse) {
        reverse = reverse.replace(".", "");
        char[] charArray = reverse.toCharArray();
        StringBuilder builder = new StringBuilder();
        for (int index = charArray.length - 1; index >= 0; index--) {
            char digit = charArray[index];
            builder.append(digit);
            if (index % 4 == 0) {
                builder.append(':');
            }
        }
        String ip = builder.toString();
        return SubnetIPv6.normalizeIPv6(ip);
    }

    /**
     * Mapa de blocos IP de ASs com busca em rvore binria log2(n).
     */
    private static final TreeMap<String, SubnetIPv6> MAP = new TreeMap<String, SubnetIPv6>();

    /**
     * Remove registro de bloco de IP para AS do cache.
     * @param ip o IP cujo bloco deve ser removido.
     * @return o registro de bloco removido, se existir.
     */
    public static synchronized SubnetIPv6 removeSubnet(String ip) {
        // Busca eficiente O(log2(n)).
        // Este mtodo s funciona se o mapa no tiver interseco de blocos.
        String key = expandIPv6(ip);
        key = MAP.floorKey(key);
        if (key == null) {
            return null;
        } else {
            SubnetIPv6 subnet = MAP.remove(key);
            // Atualiza flag de atualizao.
            CHANGED = true;
            return subnet;
        }
    }

    /**
     * Flag que indica se o cache foi modificado.
     */
    private static boolean CHANGED = false;

    protected static synchronized TreeSet<Subnet> getSubnetSet() {
        TreeSet<Subnet> subnetSet = new TreeSet<Subnet>();
        subnetSet.addAll(MAP.values());
        return subnetSet;
    }

    /**
     * Atualiza o bloco de IP de AS de um determinado IP.
     * @param ip o IP cujo bloco deve ser retornado.
     * @throws ProcessException se houver falha no processamento.
     */
    public static synchronized void refreshSubnet(String ip) throws ProcessException {
        SubnetIPv6 subnet;
        String key = MAP.floorKey(expandIPv6(ip));
        while (key != null) {
            subnet = MAP.get(key);
            if (subnet.contains(ip)) {
                // Atualizando campos do registro.
                if (!subnet.refresh()) {
                    // Domnio real do resultado WHOIS no bate com o registro.
                    // Pode haver mudana na distribuio dos blocos.
                    // Apagando registro de bloco do cache.
                    MAP.remove(key);
                    CHANGED = true;
                    // Segue para nova consulta.
                    break;
                }
            } else {
                key = MAP.lowerKey(key);
            }
        }
        // No encontrou a sub-rede em cache.
        // Selecionando servidor da pesquisa WHOIS.
        String server = getWhoisServer(ip);
        // Fazer a consulta no WHOIS.
        String result = Server.whois(ip, server);
        subnet = new SubnetIPv6(result);
        subnet.server = server; // Temporrio at final de transio.
        key = getFirstIPv6(subnet.getInetnum());
        key = expandIPv6(key);
        MAP.put(key, subnet);
        CHANGED = true;
    }

    public static String getFirstIPv6(String inetnum) {
        int index = inetnum.indexOf('/');
        String ip = inetnum.substring(0, index);
        String size = inetnum.substring(index + 1);
        int sizeInt = Integer.parseInt(size);
        short[] mask = SubnetIPv6.getMaskIPv6(sizeInt);
        short[] address = SubnetIPv6.split(ip, mask);
        int p1 = address[0] & 0xFFFF;
        int p2 = address[1] & 0xFFFF;
        int p3 = address[2] & 0xFFFF;
        int p4 = address[3] & 0xFFFF;
        int p5 = address[4] & 0xFFFF;
        int p6 = address[5] & 0xFFFF;
        int p7 = address[6] & 0xFFFF;
        int p8 = address[7] & 0xFFFF;
        return Integer.toHexString(p1) + ":" + Integer.toHexString(p2) + ":" + Integer.toHexString(p3) + ":"
                + Integer.toHexString(p4) + ":" + Integer.toHexString(p5) + ":" + Integer.toHexString(p6) + ":"
                + Integer.toHexString(p7) + ":" + Integer.toHexString(p8);
    }

    public static String getLastIPv6(String inetnum) {
        int index = inetnum.indexOf('/');
        String ip = inetnum.substring(0, index);
        String size = inetnum.substring(index + 1);
        int sizeInt = Integer.parseInt(size);
        short[] mask = SubnetIPv6.getMaskIPv6(sizeInt);
        short[] address = SubnetIPv6.split(ip, mask);
        int p1 = (address[0] & 0xFFFF) ^ (~mask[0] & 0xFFFF);
        int p2 = (address[1] & 0xFFFF) ^ (~mask[1] & 0xFFFF);
        int p3 = (address[2] & 0xFFFF) ^ (~mask[2] & 0xFFFF);
        int p4 = (address[3] & 0xFFFF) ^ (~mask[3] & 0xFFFF);
        int p5 = (address[4] & 0xFFFF) ^ (~mask[4] & 0xFFFF);
        int p6 = (address[5] & 0xFFFF) ^ (~mask[5] & 0xFFFF);
        int p7 = (address[6] & 0xFFFF) ^ (~mask[6] & 0xFFFF);
        int p8 = (address[7] & 0xFFFF) ^ (~mask[7] & 0xFFFF);
        return Integer.toHexString(p1) + ":" + Integer.toHexString(p2) + ":" + Integer.toHexString(p3) + ":"
                + Integer.toHexString(p4) + ":" + Integer.toHexString(p5) + ":" + Integer.toHexString(p6) + ":"
                + Integer.toHexString(p7) + ":" + Integer.toHexString(p8);
    }

    /**
     * Corrige o endereo da notao CIDR para sem abreviao.
     * @param inetnum o endereo com notao CIDR sem abreviao.
     * @return o endereo da notao CIDR sem abreviao.
     */
    public static String normalizeCIDRv6(String inetnum) {
        if (inetnum == null) {
            return null;
        } else {
            int index = inetnum.indexOf('/');
            String ip = inetnum.substring(0, index);
            String size = inetnum.substring(index + 1);
            int sizeInt = Integer.parseInt(size);
            if (sizeInt < 0 || sizeInt > 128) {
                return null;
            } else {
                short[] mask = SubnetIPv6.getMaskIPv6(sizeInt);
                short[] address = SubnetIPv6.split(ip, mask);
                int p1 = address[0] & 0xFFFF;
                int p2 = address[1] & 0xFFFF;
                int p3 = address[2] & 0xFFFF;
                int p4 = address[3] & 0xFFFF;
                int p5 = address[4] & 0xFFFF;
                int p6 = address[5] & 0xFFFF;
                int p7 = address[6] & 0xFFFF;
                int p8 = address[7] & 0xFFFF;
                return Integer.toHexString(p1) + ":" + Integer.toHexString(p2) + ":" + Integer.toHexString(p3) + ":"
                        + Integer.toHexString(p4) + ":" + Integer.toHexString(p5) + ":" + Integer.toHexString(p6)
                        + ":" + Integer.toHexString(p7) + ":" + Integer.toHexString(p8) + "/" + sizeInt;
            }
        }
    }

    public static String getInetnum(String ip) {
        try {
            SubnetIPv6 subnet = getSubnet(ip);
            return normalizeCIDRv6(subnet.get("inetnum", false));
        } catch (ProcessException ex) {
            if (ex.getMessage().equals("ERROR: SERVER NOT FOUND")) {
                return null;
            } else if (ex.getMessage().equals("ERROR: WHOIS QUERY LIMIT")) {
                return null;
            } else if (ex.getMessage().equals("ERROR: SUBNET NOT FOUND")) {
                return null;
            } else if (ex.getMessage().equals("ERROR: WHOIS QUERY LIMIT")) {
                return null;
            } else {
                Server.logError(ex);
                return null;
            }
        }
    }

    public static String getOwnerID(String ip) {
        try {
            SubnetIPv6 subnet = getSubnet(ip);
            return subnet.get("ownerid", false);
        } catch (ProcessException ex) {
            if (ex.getMessage().equals("ERROR: SERVER NOT FOUND")) {
                return null;
            } else if (ex.getMessage().equals("ERROR: WHOIS QUERY LIMIT")) {
                return null;
            } else if (ex.getMessage().equals("ERROR: SUBNET NOT FOUND")) {
                return null;
            } else if (ex.getMessage().equals("ERROR: WHOIS QUERY LIMIT")) {
                return null;
            } else {
                Server.logError(ex);
                return null;
            }
        }
    }

    public static String getOwnerC(String ip) {
        try {
            SubnetIPv6 subnet = getSubnet(ip);
            return subnet.get("owner-c", false);
        } catch (ProcessException ex) {
            if (ex.getMessage().equals("ERROR: SERVER NOT FOUND")) {
                return null;
            } else if (ex.getMessage().equals("ERROR: WHOIS QUERY LIMIT")) {
                return null;
            } else if (ex.getMessage().equals("ERROR: SUBNET NOT FOUND")) {
                return null;
            } else if (ex.getMessage().equals("ERROR: WHOIS QUERY LIMIT")) {
                return null;
            } else {
                Server.logError(ex);
                return null;
            }
        }
    }

    private static synchronized SubnetIPv6 newSubnet(String ip) throws ProcessException {
        //        Server.logTrace("quering new WHOIS IPv6");
        // Selecionando servidor da pesquisa WHOIS.
        String server = getWhoisServer(ip);
        // Fazer a consulta no WHOIS.
        String result = Server.whois(ip, server);
        SubnetIPv6 subnet = new SubnetIPv6(result);
        subnet.server = server; // Temporrio at final de transio.
        String key = getFirstIPv6(subnet.getInetnum());
        key = expandIPv6(key);
        MAP.put(key, subnet);
        CHANGED = true;
        return subnet;
    }

    /**
     * Retorna o bloco de IP de AS de um determinado IP.
     * @param ip o IP cujo bloco deve ser retornado.
     * @return o registro de bloco IPv6 de AS de um determinado IP.
     * @throws ProcessException se houver falha no processamento.
     */
    public static SubnetIPv6 getSubnet(String ip) throws ProcessException {
        SubnetIPv6 subnet;
        String key = MAP.floorKey(expandIPv6(ip));
        while (key != null) {
            subnet = MAP.get(key);
            if (subnet.contains(ip)) {
                if (subnet.isRegistryExpired()) {
                    // Registro expirado.
                    // Atualizando campos do registro.
                    if (subnet.refresh()) {
                        // Bloco do resultado WHOIS bate com o bloco do registro.
                        return subnet;
                    } else if (MAP.remove(key) != null) {
                        // Domnio real do resultado WHOIS no bate com o registro.
                        // Pode haver mudana na distribuio dos blocos.
                        // Apagando registro de bloco do cache.
                        CHANGED = true;
                        // Segue para nova consulta.
                        break;
                    }
                } else {
                    return subnet;
                }
            } else {
                key = MAP.lowerKey(key);
            }
        }
        // No encontrou a sub-rede em cache.
        return newSubnet(ip);
    }

    private static synchronized TreeMap<String, SubnetIPv6> getMap() {
        TreeMap<String, SubnetIPv6> map = new TreeMap<String, SubnetIPv6>();
        map.putAll(MAP);
        return map;
    }

    /**
     * Armazenamento de cache em disco.
     */
    public static void store() {
        if (CHANGED) {
            try {
                //                Server.logTrace("storing subnet6.map");
                long time = System.currentTimeMillis();
                TreeMap<String, SubnetIPv6> map = getMap();
                File file = new File("./data/subnet6.map");
                FileOutputStream outputStream = new FileOutputStream(file);
                try {
                    SerializationUtils.serialize(map, outputStream);
                    // Atualiza flag de atualizao.
                    CHANGED = false;
                } finally {
                    outputStream.close();
                }
                Server.logStore(time, file);
            } catch (Exception ex) {
                Server.logError(ex);
            }
        }
    }

    private static synchronized SubnetIPv6 put(String key, SubnetIPv6 subnet) {
        return MAP.put(key, subnet);
    }

    /**
     * Carregamento de cache do disco.
     */
    public static void load() {
        long time = System.currentTimeMillis();
        File file = new File("./data/subnet6.map");
        if (file.exists()) {
            try {
                TreeMap<Object, Object> map;
                FileInputStream fileInputStream = new FileInputStream(file);
                try {
                    map = SerializationUtils.deserialize(fileInputStream);
                } finally {
                    fileInputStream.close();
                }
                for (Object value : map.values()) {
                    if (value instanceof SubnetIPv6) {
                        SubnetIPv6 sub6 = (SubnetIPv6) value;
                        sub6.normalize();
                        String cidr = sub6.getInetnum();
                        String ip = getFirstIPv6(cidr);
                        String key = expandIPv6(ip);
                        put(key, sub6);
                    }
                }
                Server.logLoad(time, file);
            } catch (Exception ex) {
                Server.logError(ex);
            }
        }
    }

    public static boolean containsIPv6(String cidr, String ip) {
        if (isValidCIDRv6(cidr) && isValidIPv6(ip)) {
            int index = cidr.lastIndexOf('/');
            String size = cidr.substring(index + 1);
            String address = cidr.substring(0, index);
            short[] mask = SubnetIPv6.getMaskIPv6(size);
            short[] address1 = SubnetIPv6.split(address, mask);
            short[] address2 = SubnetIPv6.split(ip, mask);
            return Arrays.equals(address1, address2);
        } else {
            return false;
        }
    }

    /**
     * Verifica se o endereo IP passado faz parte do bloco.
     * @param ip o endereo IP em notao IPv6.
     * @return verdadeiro se o endereo IP passado faz parte do bloco.
     */
    public boolean contains(String ip) {
        return contains(getAddressIP(ip));
    }

    /**
     * Verifica se o endereo IP passado faz parte do bloco.
     * @param ip o endereo IP em inteiro de 64 bits.
     * @return verdadeiro se o endereo IP passado faz parte do bloco.
     */
    public boolean contains(long ip) {
        return this.address == (ip & mask);
    }

    public int compareTo(SubnetIPv6 other) {
        return new Long(this.address).compareTo(other.address);
    }

    /**
     * Mapa completo dos blocos alocados aos pases.
     */
    private static final TreeMap<String, SubnetIPv6> SERVER_MAP = new TreeMap<String, SubnetIPv6>();

    /**
     * Adiciona um servidor WHOIS na lista com seu respecitivo bloco.
     * @param inetnum o endereo de bloco em notao CIDR.
     * @param server o servidor WHOIS responsvel por aquele bloco.
     */
    private static void addServer(String inetnum, String server) {
        try {
            SubnetIPv6 subnet = new SubnetIPv6(inetnum, server);
            String ip = getFirstIPv6(subnet.getInetnum());
            ip = expandIPv6(ip);
            SERVER_MAP.put(ip, subnet);
        } catch (Exception ex) {
            Server.logError(ex);
        }
    }

    // Temporrio
    @Override
    public String getWhoisServer() throws ProcessException {
        String ip = getFirstIPv6(getInetnum());
        return getWhoisServer(ip);
    }

    /**
     * Retorna o servidor que possui a informao de bloco IPv6 de AS de um IP.
     * @param address endereo IP em inteiro de 64 bits.
     * @return o servidor que possui a informao de bloco IPv6 de AS de um IP.
     * @throws QueryException se o bloco no for encontrado para o IP especificado.
     */
    private static String getWhoisServer(String ip) throws ProcessException {
        // Busca eficiente O(log2(n)).
        ip = expandIPv6(ip);
        String key = SERVER_MAP.floorKey(ip);
        if (key == null) {
            throw new ProcessException("ERROR: SERVER NOT FOUND");
        } else {
            SubnetIPv6 subnet = SERVER_MAP.get(key);
            if (subnet.contains(ip)) {
                return subnet.getServer();
            } else {
                throw new ProcessException("ERROR: SERVER NOT FOUND");
            }
        }
    }

    /**
     * Construo do mapa dos blocos alocados.
     * Temporrio at implementao de busca pelo whois.iana.org.
     */
    static {
        addServer("2001:1280::/25", Server.WHOIS_BR);
        addServer("2801:80::/26", Server.WHOIS_BR);
        addServer("2804::/16", Server.WHOIS_BR);
    }
}