com.vuze.plugin.azVPN_Helper.CheckerCommon.java Source code

Java tutorial

Introduction

Here is the source code for com.vuze.plugin.azVPN_Helper.CheckerCommon.java

Source

/*
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package com.vuze.plugin.azVPN_Helper;

import java.lang.reflect.Field;
import java.net.*;
import java.util.*;
import java.util.regex.Pattern;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.plugins.PluginConfig;
import org.gudy.azureus2.plugins.PluginException;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.installer.StandardPlugin;
import org.gudy.azureus2.plugins.utils.*;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.networkmanager.admin.*;
import com.aelitis.azureus.core.proxy.AEProxySelector;
import com.aelitis.azureus.core.proxy.AEProxySelectorFactory;
import com.aelitis.azureus.core.util.NetUtils;
import com.aelitis.net.udp.uc.PRUDPPacketHandler;
import com.aelitis.net.udp.uc.PRUDPPacketHandlerFactory;
import com.aelitis.net.udp.uc.PRUDPReleasablePacketHandler;

/**
 * Common Checker class, shared amoungst azVPN_Air, azVPN_PIA, etc
 */
public abstract class CheckerCommon {
    protected static final char CHAR_GOOD = '\u2714';

    protected static final char CHAR_BAD = '\u2716';

    protected static final char CHAR_WARN = '\u2318';

    protected static final int STATUS_ID_OK = 0;

    protected static final int STATUS_ID_BAD = 1;

    protected static final int STATUS_ID_WARN = 2;

    protected UTTimer timer;

    public PluginConfig config;

    public PluginInterface pi;

    protected String lastProtocolAddresses = "";

    protected String lastPortCheckStatus = "";

    protected boolean checkingPortBinding;

    protected LocaleUtilities texts;

    protected InetAddress testSocketAddress;

    protected InetAddress vpnIP;

    protected int currentStatusID = -1;

    private int minSubnetMaskBitCount = -1;

    public CheckerCommon() {
    }

    public CheckerCommon(PluginInterface pi) {
        this.pi = pi;
        this.config = pi.getPluginconfig();
        this.texts = pi.getUtilities().getLocaleUtilities();

        try {
            testSocketAddress = InetAddress.getByAddress(new byte[] { 8, 8, 8, 8 });
        } catch (UnknownHostException e) {
        }

    }

    public void destroy() {
        if (timer != null) {
            timer.destroy();
            timer = null;
        }
    }

    protected final void buildTimer() {
        if (timer != null) {
            timer.destroy();
            timer = null;
        }
        int mins = config.getPluginIntParameter(PluginConstants.CONFIG_CHECK_MINUTES);
        if (mins == 0) {
            return;
        }
        timer = pi.getUtilities().createTimer("VPNHelper");
        timer.addPeriodicEvent(mins * 60 * 1000l, new UTTimerEventPerformer() {
            public void perform(UTTimerEvent event) {
                try {
                    portBindingCheck();
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        });
    }

    public final String calcProtocolAddresses() {
        long now = pi.getUtilities().getCurrentSystemTime();
        StringBuilder sReply = new StringBuilder("Last Checked ")
                .append(pi.getUtilities().getFormatters().formatDate(now)).append("\n");
        // Stolen from NetworkAdminImpl.generateDiagnostics
        // This takes some time (1s-ish), so it's better to do it on demand
        try {
            NetworkAdmin networkAdmin = NetworkAdmin.getSingleton();
            AzureusCore azureus_core = AzureusCoreFactory.getSingleton();

            NetworkAdminNetworkInterfaceAddress bindTo = null;
            try {
                NetworkAdminNetworkInterface[] interfaces = networkAdmin.getInterfaces();
                for (NetworkAdminNetworkInterface networkAdminInterface : interfaces) {
                    NetworkAdminNetworkInterfaceAddress[] addresses = networkAdminInterface.getAddresses();
                    for (NetworkAdminNetworkInterfaceAddress a : addresses) {
                        InetAddress address = a.getAddress();
                        if (address.equals(vpnIP)) {
                            bindTo = a;
                            break;
                        }
                    }
                }
            } catch (Throwable e) {
            }

            NetworkAdminProtocol[] protocols = networkAdmin.getOutboundProtocols(azureus_core);

            for (int i = 0; i < protocols.length; i++) {

                NetworkAdminProtocol protocol = protocols[i];

                try {

                    //               InetAddress ext_addr = networkAdmin.testProtocol(protocol);
                    InetAddress ext_addr = protocol
                            .test(protocol.getType() == NetworkAdminProtocol.PT_HTTP ? null : bindTo);

                    String country = null;
                    if (ext_addr != null) {

                        List<LocationProvider> locationProviders = pi.getUtilities().getLocationProviders();
                        for (LocationProvider locationProvider : locationProviders) {
                            country = locationProvider.getCountryNameForIP(ext_addr, Locale.getDefault());
                            if (country != null) {
                                break;
                            }
                        }
                    }

                    addLiteralReply(sReply,
                            protocol.getName() + " - " + ext_addr + (country == null ? "" : " - " + country));

                } catch (NetworkAdminException e) {

                    addLiteralReply(sReply, protocol.getName() + " - " + Debug.getNestedExceptionMessage(e));
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            addReply(sReply, CHAR_BAD, "vpnhelper.nat.error", new String[] { e.toString() });
        }

        lastProtocolAddresses = sReply.toString();

        CheckerListener[] triggers = PluginVPNHelper.instance.getCheckerListeners();
        for (CheckerListener l : triggers) {
            try {
                l.protocolAddressesStatusChanged(lastProtocolAddresses);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return lastProtocolAddresses;
    }

    private final boolean matchesVPNIP(InetAddress address, NetworkInterface networkInterface) {
        if (address == null) {
            return false;
        }

        String[] excludes = config.getPluginStringParameter(PluginConstants.CONFIG_IGNORE_ADDRESS).split(";");
        if (excludes != null && excludes.length > 0) {
            String hostAddress = address.getHostAddress();
            for (String exclude : excludes) {
                if (hostAddress.equals(exclude)) {
                    PluginVPNHelper.log(exclude + " matched and excluded");
                    return false;
                }
            }
        }

        String regex = config.getPluginStringParameter(PluginConstants.CONFIG_VPN_IP_MATCHING);
        boolean matches = Pattern.matches(regex, address.getHostAddress());

        if (matches && excludes != null && excludes.length > 0) {
            if (networkInterface == null) {
                try {
                    networkInterface = NetUtils.getByInetAddress(address);
                } catch (SocketException e) {
                }
            }
            if (networkInterface != null) {
                String name = networkInterface.getName();
                for (String exclude : excludes) {
                    if (exclude.equals(name)) {
                        PluginVPNHelper.log(exclude + " matched and excluded");
                        return false;
                    }
                }
            }
        }

        return matches;
    }

    protected final int handleFindBindingAddress(InetAddress currentBindIP, StringBuilder sReply) {
        return handleFindBindingAddress(currentBindIP, sReply, 0);
    }

    private final int handleFindBindingAddress(InetAddress currentBindIP, StringBuilder sReply, int numLoops) {
        if (currentBindIP == null) {
            addReply(sReply, CHAR_BAD, "!Bind IP null!", new String[] { "" + currentBindIP });
            return STATUS_ID_BAD;
        }

        int newStatusID = STATUS_ID_OK;

        Map<String, BindableInterface> mapBindableInterfaces = new HashMap<String, BindableInterface>();

        BindableInterface newBind = null;

        String s;

        // The "Any" field is equivalent to 0.0.0.0 in dotted-quad notation, which is unbound.
        // "Loopback" is 127.0.0.1, which is bound when Vuze can't bind to
        // user specified interface (ie. kill switched)
        if (currentBindIP.isAnyLocalAddress()) {
            addReply(sReply, CHAR_WARN, "vpnhelper.vuze.unbound");
        } else if (currentBindIP.isLoopbackAddress()) {
            addReply(sReply, CHAR_BAD, "vpnhelper.vuze.loopback");
        } else {
            // bound
            boolean isGoodExistingBind = matchesVPNIP(currentBindIP, null);
            if (isGoodExistingBind) {
                String niName = "Unknown Interface";
                try {
                    NetworkInterface networkInterface = NetUtils.getByInetAddress(currentBindIP);
                    niName = networkInterface.getName() + " (" + networkInterface.getDisplayName() + ")";
                } catch (Throwable e) {
                }
                addReply(sReply, CHAR_GOOD, "vpnhelper.bound.good", new String[] { "" + currentBindIP, niName });
                vpnIP = currentBindIP;
            } else {
                addReply(sReply, CHAR_BAD, "vpnhelper.bound.bad", new String[] { "" + currentBindIP });
            }
        }

        try {
            boolean foundExistingVPNIP = false;
            NetworkAdmin networkAdmin = NetworkAdmin.getSingleton();

            // Find a bindable address that starts with 10.
            InetAddress[] bindableAddresses = networkAdmin.getBindableAddresses();

            for (InetAddress bindableAddress : bindableAddresses) {
                if (matchesVPNIP(bindableAddress, null)) {
                    String hostAddress = bindableAddress.getHostAddress();
                    BindableInterface bi = mapBindableInterfaces.get(hostAddress);
                    if (bi == null) {
                        bi = new BindableInterface(bindableAddress, NetUtils.getByInetAddress(bindableAddress));
                        mapBindableInterfaces.put(hostAddress, bi);
                        if (!foundExistingVPNIP && bindableAddress.equals(vpnIP)) {
                            foundExistingVPNIP = true;
                        }
                    }
                }
            }

            // Find a Network Interface that has an address that starts with 10.
            NetworkAdminNetworkInterface[] interfaces = networkAdmin.getInterfaces();

            /* Test reverse *
            for (int i = 0; i < interfaces.length / 2; i++) {
               NetworkAdminNetworkInterface temp = interfaces[i];
               interfaces[i] = interfaces[interfaces.length - i - 1];
               interfaces[interfaces.length - i - 1] = temp;
            }
            /**/
            for (NetworkAdminNetworkInterface networkAdminInterface : interfaces) {
                NetworkAdminNetworkInterfaceAddress[] addresses = networkAdminInterface.getAddresses();
                for (NetworkAdminNetworkInterfaceAddress a : addresses) {
                    InetAddress address = a.getAddress();
                    if (address instanceof Inet4Address) {
                        if (matchesVPNIP(address, null)) {
                            String hostAddress = address.getHostAddress();
                            BindableInterface bi = mapBindableInterfaces.get(hostAddress);
                            if (bi == null) {
                                bi = new BindableInterface(address,
                                        NetUtils.getByName(networkAdminInterface.getName()));
                                mapBindableInterfaces.put(hostAddress, bi);
                                if (!foundExistingVPNIP && address.equals(vpnIP)) {
                                    foundExistingVPNIP = true;
                                }
                            }
                        }
                    }
                }
            }

            if (vpnIP != null && !foundExistingVPNIP) {
                String niName = "Unknown Interface";
                try {
                    NetworkInterface networkInterface = NetUtils.getByInetAddress(currentBindIP);
                    niName = networkInterface.getName() + " (" + networkInterface.getDisplayName() + ")";
                } catch (Throwable e) {
                }
                addReply(sReply, CHAR_WARN, "vpnhelper.existing.not.found",
                        new String[] { "" + currentBindIP, niName });

                if (numLoops == 0) {
                    try {
                        Field fldLastNICheck = NetUtils.class.getDeclaredField("last_ni_check");
                        fldLastNICheck.setAccessible(true);
                        fldLastNICheck.set(null, Long.valueOf(-1));
                        return handleFindBindingAddress(currentBindIP, sReply, ++numLoops);
                    } catch (Throwable t) {
                        t.printStackTrace();
                    }
                }
            }

            BindableInterface[] array = mapBindableInterfaces.values().toArray(new BindableInterface[0]);
            Arrays.sort(array);

            for (BindableInterface bi : array) {
                if (!bi.isValidPrefixLength(minSubnetMaskBitCount)) {
                    addReply(sReply, CHAR_WARN, "vpnhelper.submask.too.broad",
                            new String[] { "" + bi.address,
                                    bi.networkInterface == null ? "null"
                                            : bi.networkInterface.getName() + " ("
                                                    + bi.networkInterface.getDisplayName() + ")",
                                    "" + bi.networkPrefixLength, "" + minSubnetMaskBitCount });
                } else if (bi.canReach) {
                    addReply(sReply, CHAR_GOOD, "vpnhelper.found.bindable.vpn",
                            new String[] { "" + bi.address,
                                    bi.networkInterface == null ? "null"
                                            : bi.networkInterface.getName() + " ("
                                                    + bi.networkInterface.getDisplayName() + ")" });
                } else {
                    addReply(sReply, CHAR_WARN, "vpnhelper.not.reachable",
                            new String[] { "" + bi.address,
                                    bi.networkInterface == null ? "null"
                                            : bi.networkInterface.getName() + " ("
                                                    + bi.networkInterface.getDisplayName() + ")" });
                }
                PluginVPNHelper.log("subnet: " + bi.networkPrefixLength + "; Score: " + bi.score);
            }
            newBind = array.length > 0 && array[0].canReach && array[0].isValidPrefixLength(minSubnetMaskBitCount)
                    ? array[0]
                    : null;

            InetAddress localAddress = null;

            // Check if default routing goes through 10.*, by connecting to address
            // via socket.  Address doesn't need to be reachable, just routable.
            // This works on Windows, but on Mac returns a wildcard address
            DatagramSocket socket = new DatagramSocket();
            try {
                socket.connect(testSocketAddress, 0);
                localAddress = socket.getLocalAddress();
            } finally {
                socket.close();
            }

            if (localAddress != null && !localAddress.isAnyLocalAddress()) {
                NetworkInterface networkInterface = NetUtils.getByInetAddress(localAddress);

                s = texts.getLocalisedMessageText("vpnhelper.nonvuze.probable.route",
                        new String[] { "" + localAddress, networkInterface == null ? "null"
                                : networkInterface.getName() + " (" + networkInterface.getDisplayName() + ")" });

                if ((localAddress instanceof Inet4Address) && matchesVPNIP(localAddress, networkInterface)) {

                    if (newBind == null) {

                        int networkPrefixLength = getNetworkPrefixLength(networkInterface, localAddress);
                        if (networkPrefixLength >= 0 && networkPrefixLength < minSubnetMaskBitCount) {
                            s = null;
                            addReply(sReply, CHAR_WARN, "vpnhelper.nonvuze.submask.too.broad",
                                    new String[] { "" + localAddress,
                                            networkInterface == null ? "null"
                                                    : networkInterface.getName() + " ("
                                                            + networkInterface.getDisplayName() + ")",
                                            "" + networkPrefixLength, "" + minSubnetMaskBitCount });
                        } else if (!canReach(localAddress)) {
                            addReply(sReply, CHAR_WARN, "vpnhelper.not.reachable",
                                    new String[] { "" + localAddress,
                                            networkInterface == null ? "null"
                                                    : networkInterface.getName() + " ("
                                                            + networkInterface.getDisplayName() + ")" });
                        } else {
                            newBind = new BindableInterface(localAddress, networkInterface);

                            s = CHAR_GOOD + " " + s + " " + texts.getLocalisedMessageText("vpnhelper.assuming.vpn");
                        }
                    } else if (localAddress.equals(newBind.address)) {
                        s = CHAR_GOOD + " " + s + " " + texts.getLocalisedMessageText("vpnhelper.same.address");
                    } else {
                        // Vuze not bound. We already found a boundable address, but it's not this one
                        /* Possibly good case:
                         * - Vuze: unbound
                         * - Found Bindable: 10.100.1.6
                         * - Default Routing: 10.255.1.1
                         * -> Split network
                         */
                        if (newStatusID != STATUS_ID_BAD) {
                            newStatusID = STATUS_ID_WARN;
                        }
                        s = CHAR_WARN + " " + s + " "
                                + texts.getLocalisedMessageText("vpnhelper.not.same.future.address") + " "
                                + texts.getLocalisedMessageText("default.routing.not.vpn.network.splitting") + " "
                                + texts.getLocalisedMessageText(
                                        "default.routing.not.vpn.network.splitting.unbound");
                    }

                    if (s != null) {
                        addLiteralReply(sReply, s);
                    }

                } else {
                    s = CHAR_WARN + " " + s;
                    if (!currentBindIP.isLoopbackAddress()) {
                        s += " " + texts.getLocalisedMessageText("default.routing.not.vpn.network.splitting");
                    }

                    if (newBind == null) {
                        if (newStatusID != STATUS_ID_BAD) {
                            newStatusID = STATUS_ID_WARN;
                        }
                        s += " " + texts
                                .getLocalisedMessageText("default.routing.not.vpn.network.splitting.unbound");
                    }

                    addLiteralReply(sReply, s);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            addReply(sReply, CHAR_BAD, "vpnhelper.nat.error", new String[] { e.toString() });
        }

        if (newBind == null) {
            addReply(sReply, CHAR_BAD, "vpnhelper.vpn.ip.detect.fail");

            String configBindIP = config.getCoreStringParameter(PluginConfig.CORE_PARAM_STRING_LOCAL_BIND_IP);

            if (configBindIP != null && configBindIP.length() > 0) {
                addReply(sReply, CHAR_WARN,
                        "vpnhelper" + (currentBindIP.isLoopbackAddress() ? ".existing.bind.kept.loopback"
                                : ".existing.bind.kept"),
                        new String[] { configBindIP });

                if (currentBindIP.isLoopbackAddress()) {
                    if (numLoops == 0) {
                        try {
                            Field fldLastNICheck = NetUtils.class.getDeclaredField("last_ni_check");
                            fldLastNICheck.setAccessible(true);
                            fldLastNICheck.set(null, Long.valueOf(-1));
                            return handleFindBindingAddress(currentBindIP, sReply, ++numLoops);
                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    }
                }
            }

            return STATUS_ID_BAD;
        }

        rebindNetworkInterface(newBind.networkInterface, newBind.address, sReply);
        return newStatusID;
    }

    /**
     * @return rebind sucessful, or rebinding to already bound address
     */
    private final boolean rebindNetworkInterface(NetworkInterface networkInterface, InetAddress onlyToAddress,
            final StringBuilder sReply) {
        vpnIP = onlyToAddress;

        config.setUnsafeBooleanParameter("Enforce Bind IP", true);
        config.setUnsafeBooleanParameter("Check Bind IP On Start", true);
        config.setUnsafeBooleanParameter("Plugin.UPnP.upnp.enable", false);
        config.setUnsafeBooleanParameter("Plugin.UPnP.natpmp.enable", false);

        /**
        if (true) {
           sReply.append("Would rebind to " + networkInterface.getDisplayName()
           + onlyToAddress + "\n");
           return false;
        }
        /**/

        String ifName = networkInterface.getName();

        String configBindIP = config.getCoreStringParameter(PluginConfig.CORE_PARAM_STRING_LOCAL_BIND_IP);

        int bindNetworkInterfaceIndex = -1;
        if (onlyToAddress != null) {
            Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
            for (int j = 0; inetAddresses.hasMoreElements(); j++) {
                InetAddress element = inetAddresses.nextElement();
                if (element.equals(onlyToAddress)) {
                    bindNetworkInterfaceIndex = j;
                    break;
                }
            }
        }

        if (configBindIP.equals(ifName) || (bindNetworkInterfaceIndex >= 0
                && configBindIP.equals(ifName + "[" + bindNetworkInterfaceIndex + "]"))) {

            addReply(sReply, CHAR_GOOD, "vpnhelper.already.bound.good", new String[] { ifName });
        } else {
            String newConfigBindIP = ifName;
            if (bindNetworkInterfaceIndex >= 0) {
                newConfigBindIP += "[" + bindNetworkInterfaceIndex + "]";
            }

            final AESemaphore sem = new AESemaphore("VPNHelper BindWait");

            NetworkAdmin.getSingleton().addPropertyChangeListener(new NetworkAdminPropertyChangeListener() {
                public void propertyChanged(String property) {
                    if (property.equals(NetworkAdmin.PR_DEFAULT_BIND_ADDRESS)) {
                        sem.releaseForever();
                        NetworkAdmin.getSingleton().removePropertyChangeListener(this);

                        addReply(sReply, CHAR_GOOD, "vpnhelper.bind.complete.triggered");
                    }
                }
            });

            // I think setting CORE_PARAM_STRING_LOCAL_BIND_IP is actually synchronous
            // We set up a PropertyChangeListener in case it ever becomes asynchronous
            config.setCoreStringParameter(PluginConfig.CORE_PARAM_STRING_LOCAL_BIND_IP, newConfigBindIP);

            addReply(sReply, CHAR_GOOD, "vpnhelper.change.binding",
                    new String[] { "" + newConfigBindIP, networkInterface == null ? "null"
                            : networkInterface.getName() + " (" + networkInterface.getDisplayName() + ")" });

            sem.reserve(11000);
            return sem.isReleasedForever();
        }
        return true;
    }

    protected final void addReply(StringBuilder sReply, char prefix, String id) {
        String s = (prefix == 0 ? "" : prefix + " ") + texts.getLocalisedMessageText(id);
        addLiteralReply(sReply, s);
    }

    protected final void addReply(StringBuilder sReply, char prefix, String id, String[] params) {
        String s = (prefix == 0 ? "" : prefix + " ") + (texts == null ? "!" + id + "!" + Arrays.toString(params)
                : texts.getLocalisedMessageText(id, params));
        addLiteralReply(sReply, s);
    }

    protected final void addLiteralReply(StringBuilder sReply, String s) {
        sReply.append(s).append("\n");
        PluginVPNHelper.log(s);
    }

    protected final void changePort(int port, StringBuilder sReply) {

        PluginConfig pluginConfig = pi.getPluginconfig();
        int coreTCPPort = pluginConfig.getCoreIntParameter(PluginConfig.CORE_PARAM_INT_INCOMING_TCP_PORT);
        int coreUDPPort = pluginConfig.getCoreIntParameter(PluginConfig.CORE_PARAM_INT_INCOMING_UDP_PORT);
        if (coreTCPPort != port) {
            pluginConfig.setCoreIntParameter(PluginConfig.CORE_PARAM_INT_INCOMING_TCP_PORT, port);
            addReply(sReply, CHAR_GOOD, "vpnhelper.changed.port",
                    new String[] { "TCP", Integer.toString(coreTCPPort), Integer.toString(port) });
        }
        if (coreUDPPort != port) {
            pluginConfig.setCoreIntParameter(PluginConfig.CORE_PARAM_INT_INCOMING_UDP_PORT, port);
            addReply(sReply, CHAR_GOOD, "vpnhelper.changed.port",
                    new String[] { "UDP", Integer.toString(coreUDPPort), Integer.toString(port) });
        }
    }

    public final int getCurrentStatusID() {
        return currentStatusID;
    }

    protected final int findBindingAddress(StringBuilder sReply) {
        int newStatusID = -1;

        // Find our VPN binding (interface) address.  Checking UDP is the best bet,
        // since TCP and http might be proxied
        List<PRUDPPacketHandler> handlers = PRUDPPacketHandlerFactory.getHandlers();
        if (handlers.size() == 0) {
            PRUDPReleasablePacketHandler releasableHandler = PRUDPPacketHandlerFactory.getReleasableHandler(0);
            handlers = PRUDPPacketHandlerFactory.getHandlers();
            releasableHandler.release();
        }
        if (handlers.size() == 0) {
            addLiteralReply(sReply, CHAR_BAD + " No UDP Handlers");

            newStatusID = STATUS_ID_BAD;
        } else {

            InetAddress currentBindIP = null;
            for (PRUDPPacketHandler handler : handlers) {
                currentBindIP = handler.getBindIP();
                if (currentBindIP != null) {
                    break;
                }
            }
            newStatusID = handleFindBindingAddress(currentBindIP, sReply);
        }
        return newStatusID;
    }

    public final String portBindingCheck() {
        synchronized (this) {
            if (checkingPortBinding) {
                return lastPortCheckStatus;
            }
            checkingPortBinding = true;
        }

        CheckerListener[] triggers = PluginVPNHelper.instance.getCheckerListeners();
        for (CheckerListener l : triggers) {
            try {
                l.portCheckStart();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        StringBuilder sReply = new StringBuilder();

        try {
            int newStatusID = findBindingAddress(sReply);

            boolean doPortForwarding = config.getPluginBooleanParameter(PluginConstants.CONFIG_DO_PORT_FORWARDING);

            if (doPortForwarding) {
                boolean rpcCalled = false;
                if (newStatusID != STATUS_ID_BAD && vpnIP != null) {
                    rpcCalled = callRPCforPort(vpnIP, sReply);
                }

                if (!rpcCalled) {
                    if (newStatusID != STATUS_ID_BAD) {
                        newStatusID = STATUS_ID_WARN;

                        addReply(sReply, CHAR_WARN, "vpnhelper.port.forwarding.get.failed");
                    }
                }
            }

            if (newStatusID != -1) {
                currentStatusID = newStatusID;
            }
            String msgID = null;
            if (newStatusID == STATUS_ID_BAD) {
                msgID = "vpnhelper.topline.bad";
            } else if (newStatusID == STATUS_ID_OK) {
                msgID = "vpnhelper.topline.ok";
            } else if (newStatusID == STATUS_ID_WARN) {
                msgID = "vpnhelper.topline.warn";
            }
            if (msgID != null) {
                sReply.insert(0, texts.getLocalisedMessageText(msgID) + "\n");
            }

        } catch (Throwable t) {
            t.printStackTrace();
            PluginVPNHelper.log(t.toString());
        }

        lastPortCheckStatus = sReply.toString();

        triggers = PluginVPNHelper.instance.getCheckerListeners();
        for (CheckerListener l : triggers) {
            try {
                l.portCheckStatusChanged(lastPortCheckStatus);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        synchronized (this) {
            checkingPortBinding = false;
        }
        return lastPortCheckStatus;
    }

    protected abstract boolean callRPCforPort(InetAddress vpnIP, StringBuilder sReply);

    protected abstract boolean canReach(InetAddress addressToReach);

    protected boolean canReach(InetAddress addressToReach, URI uri) {

        InetAddress[] resolve = null;
        try {
            String domain = uri.getHost();

            // If Vuze has a proxy set up (Tools->Options->Connection->Proxy), then
            // we'll need to disable it for the URL
            AEProxySelector selector = AEProxySelectorFactory.getSelector();
            if (selector != null) {
                resolve = SystemDefaultDnsResolver.INSTANCE.resolve(domain);

                for (InetAddress address : resolve) {
                    selector.setProxy(new InetSocketAddress(address, 443), Proxy.NO_PROXY);
                }
            }

            HttpHead getHead = new HttpHead(uri);
            RequestConfig requestConfig = RequestConfig.custom().setLocalAddress(addressToReach)
                    .setConnectTimeout(12000).build();
            getHead.setConfig(requestConfig);

            CloseableHttpResponse response = HttpClients.createDefault().execute(getHead);

            response.close();

        } catch (Throwable t) {
            t.printStackTrace();
            return false;
        } finally {
            AEProxySelector selector = AEProxySelectorFactory.getSelector();
            if (selector != null && resolve != null) {
                for (InetAddress address : resolve) {
                    AEProxySelectorFactory.getSelector().removeProxy(new InetSocketAddress(address, 443));
                }
            }
        }
        return true;
    }

    public void setMinSubnetMaskBitCount(int minSubnetBitCount) {
        this.minSubnetMaskBitCount = minSubnetBitCount;
    }

    public int getNetworkPrefixLength(NetworkInterface networkInterface, InetAddress address) {
        int networkPrefixLength = -1;
        List<InterfaceAddress> interfaceAddresses = networkInterface.getInterfaceAddresses();
        for (InterfaceAddress interfaceAddress : interfaceAddresses) {
            if (!interfaceAddress.getAddress().equals(address)) {
                continue;
            }
            networkPrefixLength = interfaceAddress.getNetworkPrefixLength();
            // JDK-7107883 : getNetworkPrefixLength() does not return correct prefix length
            // networkPrefixLength will be zero on Java <= 7 when there is no
            // Broadcast address.
            // I'm guessing there is no broadcast address returned when mask is 32
            // on linux, but I can't confirm (I've seen it though)
            if (networkPrefixLength == 0 && interfaceAddress.getBroadcast() == null) {
                networkPrefixLength = 32;
            }
        }
        return networkPrefixLength;
    }

    private class BindableInterface implements Comparable<BindableInterface> {
        public InetAddress address;

        public boolean canReach;

        public int networkPrefixLength = -1;

        public NetworkInterface networkInterface;

        public int score;

        public BindableInterface(InetAddress address, NetworkInterface networkInterface) {
            this.address = address;
            this.networkInterface = networkInterface;
            this.canReach = canReach(address);

            if (networkInterface != null) {
                String name = networkInterface.getName();
                String displayName = networkInterface.getDisplayName();

                if (displayName.contains("VPN")) {
                    score += 2;
                } else if (displayName.contains("TAP")) {
                    score++;
                } else if (name.startsWith("eth")) {
                    score--;
                }

                networkPrefixLength = getNetworkPrefixLength(networkInterface, address);
            }
        }

        public boolean isValidPrefixLength(int minMask) {
            return networkPrefixLength == -1 || networkPrefixLength >= minMask;
        }

        /* (non-Javadoc)
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        public int compareTo(BindableInterface o) {
            // canReach at top
            int i = Boolean.valueOf(o.canReach).compareTo(Boolean.valueOf(canReach));

            // Valid subnet masks at top
            if (i == 0) {
                i = Boolean.valueOf(o.isValidPrefixLength(minSubnetMaskBitCount))
                        .compareTo(Boolean.valueOf(isValidPrefixLength(minSubnetMaskBitCount)));
            }

            // Highest score at top
            if (i == 0) {
                i = Integer.valueOf(o.score).compareTo(Integer.valueOf(score));
            }

            // most restrictive subnet mask at top
            if (i == 0) {
                i = Integer.valueOf(o.networkPrefixLength).compareTo(Integer.valueOf(networkPrefixLength));
            }
            return i;
        }
    }
}