com.vuze.plugin.azVPN_PIA.Checker.java Source code

Java tutorial

Introduction

Here is the source code for com.vuze.plugin.azVPN_PIA.Checker.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_PIA;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.Pattern;

import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.message.BasicNameValuePair;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.PluginConfig;
import org.gudy.azureus2.plugins.PluginInterface;
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.util.JSONUtils;
import com.aelitis.net.udp.uc.PRUDPPacketHandler;
import com.aelitis.net.udp.uc.PRUDPPacketHandlerFactory;
import com.aelitis.net.udp.uc.PRUDPReleasablePacketHandler;

public class Checker {
    private static final char CHAR_GOOD = '\u2714';

    private static final char CHAR_BAD = '\u2716';

    private static final char CHAR_WARN = '\u2318';

    public static final int STATUS_ID_OK = 0;

    public static final int STATUS_ID_BAD = 1;

    public static final int STATUS_ID_WARN = 2;

    // Is it always 70000? who knows
    private static final int STATUS_FILE_PORT_INDEX = 70000;

    private static final String PIA_DOMAIN = "www.privateinternetaccess.com";

    private static final String PIA_RPC_URL = "https://" + PIA_DOMAIN + "/vpninfo/port_forward_assignment";

    private static final boolean rebindNetworkInterface = true;

    private UTTimer timer;

    private PluginConfig config;

    private PluginInterface pi;

    private List<CheckerListener> listeners = new ArrayList<CheckerListener>(1);

    private String lastProtocolAddresses = "";

    private String lastPortCheckStatus = "";

    private boolean checkingPortBinding;

    private LocaleUtilities texts;

    private InetAddress testSocketAddress;

    private InetAddress vpnIP;

    private int currentStatusID = -1;

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

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

    }

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

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

    protected String getDefaultUsername() {
        try {
            String pathPIAManager = config.getPluginStringParameter(PluginPIA.CONFIG_PIA_MANAGER_DIR);

            File pathPIAManagerData = new File(pathPIAManager, "data");

            // settings.json has the user name
            File fileSettings = new File(pathPIAManagerData, "settings.json");
            if (!fileSettings.isFile() || !fileSettings.canRead()) {
                return "";
            }
            String settingsText = FileUtil.readFileAsString(fileSettings, -1);
            Map<?, ?> mapSettings = JSONUtils.decodeJSON(settingsText);
            String user = (String) mapSettings.get("user");

            return user;
        } catch (Exception e) {
            return "";
        }
    }

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

        CheckerListener[] triggers = listeners.toArray(new CheckerListener[0]);
        for (CheckerListener l : triggers) {
            try {
                l.portCheckStart();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        StringBuilder sReply = new StringBuilder();

        String pathPIAManager = config.getPluginStringParameter(PluginPIA.CONFIG_PIA_MANAGER_DIR);

        File pathPIAManagerData = new File(pathPIAManager, "data");

        try {
            int newStatusID = findBindingAddress(pathPIAManagerData, sReply);

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

            if (!rpcCalled) {
                boolean gotPort = checkStatusFileForPort(pathPIAManagerData, sReply);
                if (!gotPort) {
                    if (newStatusID != STATUS_ID_BAD) {
                        newStatusID = STATUS_ID_WARN;

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

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

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

        lastPortCheckStatus = sReply.toString();

        triggers = listeners.toArray(new CheckerListener[0]);
        for (CheckerListener l : triggers) {
            try {
                l.portCheckStatusChanged(lastPortCheckStatus);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

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

    private boolean checkStatusFileForPort(File pathPIAManagerData, StringBuilder sReply) {
        // Read the status_file for forwarding port

        boolean gotValidPort = false;

        File fileStatus = new File(pathPIAManagerData, "status_file.txt");
        if (!fileStatus.isFile() || !fileStatus.canRead()) {
            return false;
        }
        try {
            byte[] statusFileBytes = FileUtil.readFileAsByteArray(fileStatus);

            if (statusFileBytes.length > STATUS_FILE_PORT_INDEX && statusFileBytes[STATUS_FILE_PORT_INDEX] == '{') {
                int endPos = STATUS_FILE_PORT_INDEX;
                while (endPos < statusFileBytes.length && statusFileBytes[endPos] > 1) {
                    endPos++;
                }
                boolean gotPort = false;

                String jsonPort = new String(statusFileBytes, STATUS_FILE_PORT_INDEX,
                        endPos - STATUS_FILE_PORT_INDEX);
                Map<?, ?> decodeJSON = JSONUtils.decodeJSON(jsonPort);
                if (decodeJSON.containsKey("single")) {
                    Object oPort = decodeJSON.get("single");
                    if (oPort == null) {
                        gotPort = true;

                        String user = config.getPluginStringParameter(PluginPIA.CONFIG_USER);
                        byte[] pass = config.getPluginByteParameter(PluginPIA.CONFIG_P);

                        if (user == null || user.length() == 0 || pass == null || pass.length == 0) {

                            boolean portForwardEnabled = false;
                            File fileSettings = new File(pathPIAManagerData, "settings.json");
                            String settingsString = FileUtil.readFileAsString(fileSettings, -1);
                            Map<?, ?> mapSettings = JSONUtils.decodeJSON(settingsString);
                            if (mapSettings != null && mapSettings.containsKey("portforward")) {
                                portForwardEnabled = (Boolean) mapSettings.get("portforward");
                            }

                            addReply(sReply, CHAR_WARN,
                                    portForwardEnabled ? "pia.no.forwarding.port" : "pia.no.port.config");
                        }

                    }
                    if (oPort instanceof Number) {
                        gotPort = true;
                        gotValidPort = true;

                        Number nPort = (Number) oPort;
                        int port = nPort.intValue();

                        addReply(sReply, CHAR_GOOD, "pia.port.in.manager", new String[] { Integer.toString(port) });

                        changePort(port, sReply);
                    }
                }

                if (!gotPort) {
                    addReply(sReply, CHAR_BAD, "pia.invalid.port.status_file", new String[] { jsonPort });
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return gotValidPort;
    }

    public 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();

            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);

                    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, "pia.nat.error", new String[] { e.toString() });
        }

        lastProtocolAddresses = sReply.toString();

        CheckerListener[] triggers = listeners.toArray(new CheckerListener[0]);
        for (CheckerListener l : triggers) {
            try {
                l.protocolAddressesStatusChanged(lastProtocolAddresses);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return lastProtocolAddresses;
    }

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

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

    private void addLiteralReply(StringBuilder sReply, String s) {
        sReply.append(s).append("\n");
        PluginPIA.log(s);
    }

    private int findBindingAddress(File pathPIAManagerData, 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 bindIP = handlers.get(0).getBindIP();

            // 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 (bindIP.isAnyLocalAddress() || bindIP.isLoopbackAddress()) {
                newStatusID = handleUnboundOrLoopback(bindIP, sReply);
                if (newStatusID == STATUS_ID_BAD) {
                    return newStatusID;
                }
            } else {
                newStatusID = handleBound(bindIP, sReply);
            }
        }
        return newStatusID;
    }

    private boolean callRPCforPort(File pathPIAManagerData, InetAddress bindIP, StringBuilder sReply) {
        InetAddress[] resolve = null;
        try {
            // Let's assume the client_id.txt file is the one for port forwarding.
            File fileClientID = new File(pathPIAManagerData, "client_id.txt");
            String clientID;
            if (fileClientID.isFile() && fileClientID.canRead()) {
                clientID = FileUtil.readFileAsString(fileClientID, -1);
            } else {
                clientID = config.getPluginStringParameter("client.id", null);
                if (clientID == null) {
                    clientID = RandomUtils.generateRandomAlphanumerics(20);
                    config.setPluginParameter("client.id", clientID);
                }
            }

            HttpPost post = new HttpPost(PIA_RPC_URL);

            String user = config.getPluginStringParameter(PluginPIA.CONFIG_USER);
            String pass = new String(config.getPluginByteParameter(PluginPIA.CONFIG_P, new byte[0]), "utf-8");

            if (user == null || user.length() == 0 || pass == null || pass.length() == 0) {
                return false;
            }

            List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
            urlParameters.add(new BasicNameValuePair("user", user));
            urlParameters.add(new BasicNameValuePair("pass", pass));
            urlParameters.add(new BasicNameValuePair("client_id", clientID));
            urlParameters.add(new BasicNameValuePair("local_ip", bindIP.getHostAddress()));

            // Call needs to be from the VPN interface (the bindIP)
            RequestConfig requestConfig = RequestConfig.custom().setLocalAddress(bindIP).setConnectTimeout(10000)
                    .build();

            post.setConfig(requestConfig);

            post.setEntity(new UrlEncodedFormEntity(urlParameters));

            CloseableHttpClient httpClient = HttpClients.createDefault();

            // 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(PIA_DOMAIN);

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

            CloseableHttpResponse response = httpClient.execute(post);
            BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

            StringBuffer result = new StringBuffer();
            String line = "";
            while ((line = rd.readLine()) != null) {
                result.append(line);
            }

            boolean gotPort = false;
            // should be {"port":xyz}

            Map<?, ?> mapResult = JSONUtils.decodeJSON(result.toString());
            if (mapResult.containsKey("port")) {
                Object oPort = mapResult.get("port");
                if (oPort instanceof Number) {
                    gotPort = true;
                    Number nPort = (Number) oPort;
                    int port = nPort.intValue();

                    addReply(sReply, CHAR_GOOD, "pia.port.from.rpc", new String[] { Integer.toString(port) });

                    changePort(port, sReply);
                }
            }

            if (!gotPort) {
                addReply(sReply, CHAR_WARN, "pia.rpc.bad", new String[] { result.toString() });

                // mapResult.containsKey("error")
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            addReply(sReply, CHAR_BAD, "pia.rpc.no.connect", new String[] { bindIP + ": " + e.getMessage() });

            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;
    }

    private boolean matchesVPNIP(InetAddress address) {
        if (address == null) {
            return false;
        }
        String regex = config.getPluginStringParameter(PluginPIA.CONFIG_VPN_IP_MATCHING);
        return Pattern.matches(regex, address.getHostAddress());
    }

    private int handleBound(InetAddress bindIP, StringBuilder sReply) {
        int newStatusID = STATUS_ID_OK;

        String s;
        boolean isGoodExistingBind = matchesVPNIP(bindIP);
        if (isGoodExistingBind) {
            String niName = "Unknown Interface";
            try {
                NetworkInterface networkInterface = NetworkInterface.getByInetAddress(bindIP);
                niName = networkInterface.getName() + " (" + networkInterface.getDisplayName() + ")";
            } catch (Throwable e) {
            }
            addReply(sReply, CHAR_GOOD, "pia.bound.good", new String[] { "" + bindIP, niName });
            vpnIP = bindIP;
        } else {
            addReply(sReply, CHAR_BAD, "pia.bound.bad", new String[] { "" + bindIP });
            newStatusID = STATUS_ID_BAD;
        }

        try {
            // 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 (in some cases), but on Mac returns a wildcard 
            // address
            DatagramSocket socket = new DatagramSocket();
            socket.connect(testSocketAddress, 0);
            InetAddress localAddress = socket.getLocalAddress();
            socket.close();

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

                s = texts.getLocalisedMessageText("pia.nonvuze.probable.route", new String[] { "" + localAddress,
                        networkInterface.getName() + " (" + networkInterface.getDisplayName() + ")" });
                char replyChar = ' ';

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

                    if (localAddress.equals(bindIP)) {
                        replyChar = isGoodExistingBind ? CHAR_GOOD : CHAR_WARN;
                        s += " " + texts.getLocalisedMessageText("pia.same.as.vuze");
                    } else {
                        // Vuze is bound, default routing goes somewhere else
                        // This is ok, since Vuze will not accept incoming from "somewhere else"
                        // We'll warn, but not update the status id

                        replyChar = CHAR_WARN;
                        s += " " + texts.getLocalisedMessageText("pia.not.same");

                        if (isGoodExistingBind) {
                            s += " " + texts.getLocalisedMessageText("default.routing.not.vpn.network.splitting");
                        }
                    }

                    addLiteralReply(sReply, replyChar + " " + s);

                    if (!isGoodExistingBind && rebindNetworkInterface) {
                        rebindNetworkInterface(networkInterface, localAddress, sReply);
                        // Should we redo test?
                    }

                } else {
                    // Vuze is bound, default routing goes to somewhere else.
                    // Probably network splitting
                    replyChar = isGoodExistingBind ? CHAR_WARN : CHAR_BAD;
                    if (isGoodExistingBind) {
                        s += " " + texts.getLocalisedMessageText("default.routing.not.vpn.network.splitting");
                    }
                    addLiteralReply(sReply, replyChar + " " + s);
                }
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return newStatusID;
    }

    private int handleUnboundOrLoopback(InetAddress bindIP, StringBuilder sReply) {

        int newStatusID = STATUS_ID_OK;

        InetAddress newBindIP = null;
        NetworkInterface newBindNetworkInterface = null;

        String s;

        if (bindIP.isAnyLocalAddress()) {
            addReply(sReply, CHAR_WARN, "pia.vuze.unbound");
        } else {
            addReply(sReply, CHAR_BAD, "pia.vuze.loopback");
        }

        try {
            NetworkAdmin networkAdmin = NetworkAdmin.getSingleton();

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

            for (InetAddress bindableAddress : bindableAddresses) {
                if (matchesVPNIP(bindableAddress)) {
                    newBindIP = bindableAddress;
                    newBindNetworkInterface = NetworkInterface.getByInetAddress(newBindIP);

                    addReply(sReply, CHAR_GOOD, "pia.found.bindable.vpn", new String[] { "" + newBindIP });

                    break;
                }
            }

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

            boolean foundNIF = false;
            for (NetworkAdminNetworkInterface networkAdminInterface : interfaces) {
                NetworkAdminNetworkInterfaceAddress[] addresses = networkAdminInterface.getAddresses();
                for (NetworkAdminNetworkInterfaceAddress a : addresses) {
                    InetAddress address = a.getAddress();
                    if (address instanceof Inet4Address) {
                        if (matchesVPNIP(address)) {
                            s = texts.getLocalisedMessageText("pia.possible.vpn",
                                    new String[] { "" + address, networkAdminInterface.getName() + " ("
                                            + networkAdminInterface.getDisplayName() + ")" });

                            if (newBindIP == null) {
                                foundNIF = true;
                                newBindIP = address;

                                // Either one should work
                                //newBindNetworkInterface = NetworkInterface.getByInetAddress(newBindIP);
                                newBindNetworkInterface = NetworkInterface
                                        .getByName(networkAdminInterface.getName());

                                s = CHAR_GOOD + " " + s + ". " + texts.getLocalisedMessageText("pia.assuming.vpn");
                            } else if (address.equals(newBindIP)) {
                                s = CHAR_GOOD + " " + s + ". " + texts.getLocalisedMessageText("pia.same.address");
                                foundNIF = true;
                            } else {
                                if (newStatusID != STATUS_ID_BAD) {
                                    newStatusID = STATUS_ID_WARN;
                                }
                                s = CHAR_WARN + " " + s + ". "
                                        + texts.getLocalisedMessageText("pia.not.same.address");
                            }

                            addLiteralReply(sReply, s);

                            if (rebindNetworkInterface) {
                                // stops message below from being added, we'll rebind later
                                foundNIF = true;
                            }

                        }
                    }
                }
            }

            if (!foundNIF) {
                addReply(sReply, CHAR_BAD, "pia.interface.not.found");
            }

            // 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();
            socket.connect(testSocketAddress, 0);
            InetAddress localAddress = socket.getLocalAddress();
            socket.close();

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

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

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

                    if (newBindIP == null) {
                        newBindIP = localAddress;
                        newBindNetworkInterface = networkInterface;

                        s = CHAR_GOOD + " " + s + " " + texts.getLocalisedMessageText("pia.assuming.vpn");
                    } else if (localAddress.equals(newBindIP)) {
                        s = CHAR_GOOD + " " + s + " " + texts.getLocalisedMessageText("pia.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("pia.not.same.future.address")
                                + " " + texts.getLocalisedMessageText("default.routing.not.vpn.network.splitting")
                                + " " + texts.getLocalisedMessageText(
                                        "default.routing.not.vpn.network.splitting.unbound");
                    }

                    addLiteralReply(sReply, s);

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

                    if (newBindIP == null && foundNIF) {
                        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, "pia.nat.error", new String[] { e.toString() });
        }

        if (newBindIP == null) {
            addReply(sReply, CHAR_BAD, "pia.vpn.ip.detect.fail");
            return STATUS_ID_BAD;
        }

        rebindNetworkInterface(newBindNetworkInterface, newBindIP, sReply);
        return newStatusID;
    }

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

        /**
        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, "pia.already.bound.good", new String[] { ifName });
        } else {
            String newConfigBindIP = ifName;
            if (bindNetworkInterfaceIndex >= 0) {
                newConfigBindIP += "[" + bindNetworkInterfaceIndex + "]";
            }

            final AESemaphore sem = new AESemaphore("PIA 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, "pia.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);
            config.setUnsafeBooleanParameter("Enforce Bind IP", true);
            config.setUnsafeBooleanParameter("Check Bind IP On Start", true);

            config.setUnsafeBooleanParameter("upnp.enable", false);
            config.setUnsafeBooleanParameter("natpmp.enable", false);

            addReply(sReply, CHAR_GOOD, "pia.change.binding", new String[] { "" + newConfigBindIP,
                    networkInterface.getName() + " (" + networkInterface.getDisplayName() + ")" });

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

    protected File getPIAManagerPath() {
        File pathPIAManager = null;
        if (Constants.isWindows) {
            String pathProgFiles = System.getenv("ProgramFiles");
            if (pathProgFiles != null) {
                pathPIAManager = new File(pathProgFiles, "pia_manager");
            }
            if (pathPIAManager == null || !pathPIAManager.exists()) {
                String pathProgFiles86 = System.getenv("ProgramFiles");
                if (pathProgFiles == null && pathProgFiles86 != null) {
                    pathProgFiles86 = pathProgFiles + "(x86)";
                }
                if (pathProgFiles86 != null) {
                    pathPIAManager = new File(pathProgFiles86, "pia_manager");
                }
            }
            if (pathPIAManager == null || !pathPIAManager.exists()) {
                pathPIAManager = new File("C:\\Program Files\\pia_manager");
            }
        } else {
            pathPIAManager = new File(System.getProperty("user.home"), ".pia_manager");
        }

        return pathPIAManager;
    }

    private 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, "pia.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, "pia.changed.port",
                    new String[] { "UDP", Integer.toString(coreUDPPort), Integer.toString(port) });
        }
    }

    public void addListener(CheckerListener l) {
        listeners.add(l);
        try {
            l.portCheckStatusChanged(lastPortCheckStatus);
            l.protocolAddressesStatusChanged(lastProtocolAddresses);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void removeListener(CheckerListener l) {
        listeners.remove(l);
    }

    public int getCurrentStatusID() {
        return currentStatusID;
    }
}