Java tutorial
/* * 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_Air; import java.io.*; import java.net.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; 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.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.protocol.HttpClientContext; 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.core3.xml.simpleparser.SimpleXMLParserDocumentFactory; import org.gudy.azureus2.platform.PlatformManager; import org.gudy.azureus2.platform.PlatformManagerFactory; import org.gudy.azureus2.plugins.PluginConfig; import org.gudy.azureus2.plugins.PluginInterface; import org.gudy.azureus2.plugins.platform.PlatformManagerException; import org.gudy.azureus2.plugins.utils.*; import org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocument; import org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentAttribute; import org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentNode; 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.MapUtils; 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; private static final String VPN_DOMAIN = "airvpn.org"; private static final boolean rebindNetworkInterface = true; private static final String VPN_LOGIN_URL = "https://" + VPN_DOMAIN + "/login"; private static final String VPN_PORTS_URL = "https://" + VPN_DOMAIN + "/ports/"; private static final String REGEX_ActionURL = "action=\"([^\"]+)\""; private static final String REGEX_AuthKey = "name=['\"]auth_key['\"]\\s*value=['\"]([^\"']+)['\"]"; private static final String REGEX_Port = "class=\"ports_port\">([0-9]+)<"; private static final String REGEX_Token = "name=['\"]csrf_token['\"]\\s*value=['\"]([^\"']+)['\"]"; private static final String REGEX_PortForwardedTo = "<td>Forwarded to:</td><td>([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)</td>"; 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; private HttpClientContext httpClientContext; public Checker() { } 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(PluginAir.CONFIG_CHECK_MINUTES); if (mins == 0) { return; } timer = pi.getUtilities().createTimer("AirVPN"); timer.addPeriodicEvent(mins * 60 * 1000l, new UTTimerEventPerformer() { public void perform(UTTimerEvent event) { try { portBindingCheck(); } catch (Throwable t) { t.printStackTrace(); } } }); } protected String getDefaultUsername() { try { File vpnConfigPath = getVPNConfigPath(); if (vpnConfigPath == null) { return ""; } File fileSettings = new File(vpnConfigPath, "AirVPN.xml"); if (!fileSettings.isFile() || !fileSettings.canRead()) { return ""; } SimpleXMLParserDocument xml = SimpleXMLParserDocumentFactory.create(fileSettings); SimpleXMLParserDocumentNode options = xml.getChild("options"); SimpleXMLParserDocumentNode[] children = options.getChildren(); Map<String, String> mapOptions = new HashMap<String, String>(); for (SimpleXMLParserDocumentNode child : children) { SimpleXMLParserDocumentAttribute name = child.getAttribute("name"); if (name != null) { SimpleXMLParserDocumentAttribute value = child.getAttribute("value"); if (value != null) { mapOptions.put(name.getValue(), value.getValue()); } } } String user = MapUtils.getMapString(mapOptions, "login", ""); return user; } catch (Exception e) { PluginAir.log("Get login name: " + e.toString()); return ""; } } protected String getPassword() { try { File vpnConfigPath = getVPNConfigPath(); if (vpnConfigPath == null) { return ""; } File fileSettings = new File(vpnConfigPath, "AirVPN.xml"); if (!fileSettings.isFile() || !fileSettings.canRead()) { return ""; } SimpleXMLParserDocument xml = SimpleXMLParserDocumentFactory.create(fileSettings); SimpleXMLParserDocumentNode options = xml.getChild("options"); SimpleXMLParserDocumentNode[] children = options.getChildren(); Map<String, String> mapOptions = new HashMap<String, String>(); for (SimpleXMLParserDocumentNode child : children) { SimpleXMLParserDocumentAttribute name = child.getAttribute("name"); if (name != null) { SimpleXMLParserDocumentAttribute value = child.getAttribute("value"); if (value != null) { mapOptions.put(name.getValue(), value.getValue()); } } } String pw = MapUtils.getMapString(mapOptions, "password", ""); return pw; } catch (Exception e) { PluginAir.log("Get login creds: " + e.toString()); 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(); try { int newStatusID = findBindingAddress(sReply); boolean doPortForwarding = config.getPluginBooleanParameter(PluginAir.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, "airvpn.port.forwarding.get.failed"); } } } if (newStatusID != -1) { currentStatusID = newStatusID; } String msgID = null; if (newStatusID == STATUS_ID_BAD) { msgID = "airvpn.topline.bad"; } else if (newStatusID == STATUS_ID_OK) { msgID = "airvpn.topline.ok"; } else if (newStatusID == STATUS_ID_WARN) { msgID = "airvpn.topline.warn"; } if (msgID != null) { sReply.insert(0, texts.getLocalisedMessageText(msgID) + "\n"); } } catch (Throwable t) { t.printStackTrace(); PluginAir.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; } 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, "airvpn.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 == null ? "!" + id + "!" + Arrays.toString(params) : texts.getLocalisedMessageText(id, params)); addLiteralReply(sReply, s); } private void addLiteralReply(StringBuilder sReply, String s) { sReply.append(s).append("\n"); PluginAir.log(s); } private 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 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(InetAddress bindIP, StringBuilder sReply) { InetAddress[] resolve = null; try { String user = getDefaultUsername(); String pass = null; if (user == null || user.length() == 0) { user = config.getPluginStringParameter(PluginAir.CONFIG_USER); pass = new String(config.getPluginByteParameter(PluginAir.CONFIG_P, new byte[0]), "utf-8"); } else { pass = getPassword(); } if (user == null || user.length() == 0 || pass == null || pass.length() == 0) { addReply(sReply, CHAR_WARN, "airvpn.rpc.nocreds"); return false; } // 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(VPN_DOMAIN); for (InetAddress address : resolve) { selector.setProxy(new InetSocketAddress(address, 443), Proxy.NO_PROXY); } } RequestConfig requestConfig; StringBuffer token = new StringBuffer(); boolean skipLoginPage = false; boolean alreadyLoggedIn = false; PortInfo[] ports = null; if (httpClientContext == null) { httpClientContext = HttpClientContext.create(); } else { PluginAir.log("Have existing context. Trying to grab port list without logging in."); ports = scrapePorts(bindIP, token); // assume no token means we aren't logged in if (token.length() > 0) { PluginAir.log("Valid ports page. Skipping Login"); skipLoginPage = true; alreadyLoggedIn = true; } else { ports = null; } } if (!skipLoginPage) { String loginURL = null; String authKey = null; PluginAir.log("Getting Login post URL and auth_key"); HttpGet getLoginPage = new HttpGet(VPN_LOGIN_URL); requestConfig = RequestConfig.custom().setLocalAddress(bindIP).setConnectTimeout(10000).build(); getLoginPage.setConfig(requestConfig); CloseableHttpClient httpClientLoginPage = HttpClients.createDefault(); CloseableHttpResponse loginPageResponse = httpClientLoginPage.execute(getLoginPage, httpClientContext); BufferedReader rd = new BufferedReader( new InputStreamReader(loginPageResponse.getEntity().getContent())); Pattern patAuthKey = Pattern.compile(REGEX_AuthKey); String line = ""; while ((line = rd.readLine()) != null) { if (line.contains("<form") && line.matches(".*id=['\"]login['\"].*")) { Matcher matcher = Pattern.compile(REGEX_ActionURL).matcher(line); if (matcher.find()) { loginURL = matcher.group(1); if (authKey != null) { break; } } } Matcher matcherAuthKey = patAuthKey.matcher(line); if (matcherAuthKey.find()) { authKey = matcherAuthKey.group(1); if (loginURL != null) { break; } } if (line.contains("['member_id']") && line.matches(".*parseInt\\s*\\(\\s*[1-9][0-9]*\\s*.*")) { alreadyLoggedIn = true; } } rd.close(); if (loginURL == null) { PluginAir.log("Could not scrape Login URL. Using default"); loginURL = "https://airvpn.org/index.php?app=core&module=global§ion=login&do=process"; } if (authKey == null) { addReply(sReply, CHAR_WARN, "airvpn.rpc.noauthkey"); return false; } loginURL = UrlUtils.unescapeXML(loginURL); /////////////////////////////// if (alreadyLoggedIn) { PluginAir.log("Already Logged In"); } else { PluginAir.log("Login URL:" + loginURL); //https://airvpn.org/index.php?app=core&module=global§ion=login&do=process //https://airvpn.org/index.php?app=core&module=global§ion=login&do=process HttpPost httpPostLogin = new HttpPost(loginURL); requestConfig = RequestConfig.custom().setLocalAddress(bindIP).setConnectTimeout(10000).build(); httpPostLogin.setConfig(requestConfig); CloseableHttpClient httpClient = HttpClients.createDefault(); List<NameValuePair> urlParameters = new ArrayList<NameValuePair>(); urlParameters.add(new BasicNameValuePair("ips_username", user)); urlParameters.add(new BasicNameValuePair("ips_password", pass)); urlParameters.add(new BasicNameValuePair("auth_key", authKey)); urlParameters.add(new BasicNameValuePair("invisible", "1")); urlParameters.add(new BasicNameValuePair("inline_invisible", "1")); httpPostLogin.setEntity(new UrlEncodedFormEntity(urlParameters)); CloseableHttpResponse response = httpClient.execute(httpPostLogin, httpClientContext); rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); line = ""; while ((line = rd.readLine()) != null) { } PluginAir.log("Login Result: " + response.getStatusLine().toString()); } } //////////////////////////// if (ports == null) { ports = scrapePorts(bindIP, token); } PluginAir.log("Found Ports: " + Arrays.toString(ports)); int existingIndex = ourPortInList(ports); if (existingIndex >= 0) { addReply(sReply, CHAR_GOOD, "airvpn.port.from.rpc.match", new String[] { ports[existingIndex].port }); return true; } boolean gotPort = false; // There's a limit of 20 ports. If [0] isn't ours and 20 of them are // created, then assume our detection of "ours" is broke and just use // the first one if ((ports.length > 0 && ports[0].ourBinding) || ports.length == 20) { int port = Integer.parseInt(ports[0].port); gotPort = true; addReply(sReply, CHAR_GOOD, "airvpn.port.from.rpc", new String[] { Integer.toString(port) }); changePort(port, sReply); } else { // create port ports = createPort(bindIP, token); if (ports.length == 0) { // form post should have got the new port, but if it didn't, try // reloading the ports page again. token.setLength(0); ports = scrapePorts(bindIP, token); } PluginAir.log("Added a port. Ports: " + Arrays.toString(ports)); existingIndex = ourPortInList(ports); if (existingIndex >= 0) { addReply(sReply, CHAR_GOOD, "airvpn.port.from.rpc.match", new String[] { ports[existingIndex].port }); return true; } if ((ports.length > 0 && ports[0].ourBinding) || ports.length == 20) { int port = Integer.parseInt(ports[0].port); gotPort = true; addReply(sReply, CHAR_GOOD, "airvpn.port.from.rpc", new String[] { Integer.toString(port) }); changePort(port, sReply); } } if (!gotPort) { addReply(sReply, CHAR_WARN, "airvpn.rpc.no.connect", new String[] { bindIP.toString() }); return false; } } catch (Exception e) { e.printStackTrace(); addReply(sReply, CHAR_BAD, "airvpn.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 PortInfo[] createPort(InetAddress bindIP, StringBuffer token) throws ClientProtocolException, IOException { HttpPost httpPostCreatePort = new HttpPost(VPN_PORTS_URL); RequestConfig requestConfig = RequestConfig.custom().setLocalAddress(bindIP).setConnectTimeout(10000) .build(); httpPostCreatePort.setConfig(requestConfig); CloseableHttpClient httpClientCreatPort = HttpClients.createDefault(); List<NameValuePair> urlParamsAddPort = new ArrayList<NameValuePair>(); urlParamsAddPort.add(new BasicNameValuePair("csrf_token", token.toString())); urlParamsAddPort.add(new BasicNameValuePair("action", "ports_ins")); urlParamsAddPort.add(new BasicNameValuePair("ports_ins_port", "")); urlParamsAddPort.add(new BasicNameValuePair("ports_ins_protocol", "both")); urlParamsAddPort.add(new BasicNameValuePair("ports_ins_local", "")); urlParamsAddPort.add(new BasicNameValuePair("ports_ins_dns_name", "")); urlParamsAddPort.add(new BasicNameValuePair("ports_ins", "ports_ins")); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(urlParamsAddPort); httpPostCreatePort.setEntity(entity); CloseableHttpResponse responseCreatePort = httpClientCreatPort.execute(httpPostCreatePort, httpClientContext); BufferedReader rd = new BufferedReader(new InputStreamReader(responseCreatePort.getEntity().getContent())); String bindIPString = bindIP == null ? null : bindIP.getHostAddress(); PortInfo[] ports = parsePorts(rd, bindIPString, token); rd.close(); return ports; } private PortInfo[] scrapePorts(InetAddress bindIP, StringBuffer token) throws ClientProtocolException, IOException { String bindIPString = bindIP == null ? null : bindIP.getHostAddress(); HttpGet getPortsPage = new HttpGet(VPN_PORTS_URL); RequestConfig requestConfig = RequestConfig.custom().setLocalAddress(bindIP).setConnectTimeout(10000) .build(); getPortsPage.setConfig(requestConfig); CloseableHttpClient httpClientPortsPage = HttpClients.createDefault(); CloseableHttpResponse portsPageResponse = httpClientPortsPage.execute(getPortsPage, httpClientContext); BufferedReader rd = new BufferedReader(new InputStreamReader(portsPageResponse.getEntity().getContent())); PortInfo[] ports = parsePorts(rd, bindIPString, token); rd.close(); return ports; } private PortInfo[] parsePorts(BufferedReader rd, String bindIPString, StringBuffer token) throws IOException { Pattern patPort = Pattern.compile(REGEX_Port); Pattern patToken = Pattern.compile(REGEX_Token); Pattern patFwdToIP = Pattern.compile(REGEX_PortForwardedTo); Map<String, PortInfo> mapPorts = new HashMap<String, PortInfo>(); String line = ""; String lastPortFound = null; while ((line = rd.readLine()) != null) { Matcher matcher = patPort.matcher(line); boolean found = matcher.find(); if (found) { while (found) { lastPortFound = matcher.group(1); mapPorts.put(lastPortFound, new PortInfo(lastPortFound, false)); Matcher matcherFwdToIP = patFwdToIP.matcher(line); while (matcherFwdToIP.find()) { String ip = matcherFwdToIP.group(1); if (ip.equals(bindIPString)) { mapPorts.put(lastPortFound, new PortInfo(lastPortFound, true)); } } found = matcher.find(); } } else { if (lastPortFound != null) { Matcher matcherFwdToIP = patFwdToIP.matcher(line); while (matcherFwdToIP.find()) { String ip = matcherFwdToIP.group(1); if (ip.equals(bindIPString)) { mapPorts.put(lastPortFound, new PortInfo(lastPortFound, true)); } } } } if (token != null && token.length() == 0) { Matcher matcherToken = patToken.matcher(line); if (matcherToken.find()) { token.append(matcherToken.group(1)); } } } PortInfo[] array = mapPorts.values().toArray(new PortInfo[0]); Arrays.sort(array, new Comparator<PortInfo>() { public int compare(PortInfo arg0, PortInfo arg1) { return Boolean.compare(arg1.ourBinding, arg0.ourBinding); } }); return array; } private boolean matchesVPNIP(InetAddress address) { if (address == null) { return false; } String regex = config.getPluginStringParameter(PluginAir.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, "airvpn.bound.good", new String[] { "" + bindIP, niName }); vpnIP = bindIP; } else { addReply(sReply, CHAR_BAD, "airvpn.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("airvpn.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("airvpn.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("airvpn.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, "airvpn.vuze.unbound"); } else { addReply(sReply, CHAR_BAD, "airvpn.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, "airvpn.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("airvpn.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("airvpn.assuming.vpn"); } else if (address.equals(newBindIP)) { s = CHAR_GOOD + " " + s + ". " + texts.getLocalisedMessageText("airvpn.same.address"); foundNIF = true; } else { if (newStatusID != STATUS_ID_BAD) { newStatusID = STATUS_ID_WARN; } s = CHAR_WARN + " " + s + ". " + texts.getLocalisedMessageText("airvpn.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, "airvpn.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("airvpn.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("airvpn.assuming.vpn"); } else if (localAddress.equals(newBindIP)) { s = CHAR_GOOD + " " + s + " " + texts.getLocalisedMessageText("airvpn.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("airvpn.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, "airvpn.nat.error", new String[] { e.toString() }); } if (newBindIP == null) { addReply(sReply, CHAR_BAD, "airvpn.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, "airvpn.already.bound.good", new String[] { ifName }); } else { String newConfigBindIP = ifName; if (bindNetworkInterfaceIndex >= 0) { newConfigBindIP += "[" + bindNetworkInterfaceIndex + "]"; } final AESemaphore sem = new AESemaphore("AirVPN 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, "airvpn.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, "airvpn.change.binding", new String[] { "" + newConfigBindIP, networkInterface.getName() + " (" + networkInterface.getDisplayName() + ")" }); sem.reserve(11000); return sem.isReleasedForever(); } return true; } protected File getVPNConfigPath() { PlatformManager platformManager = PlatformManagerFactory.getPlatformManager(); try { File fDocPath = platformManager.getLocation(PlatformManager.LOC_DOCUMENTS); if (fDocPath != null) { File f = new File(fDocPath.getParentFile(), Constants.isLinux ? ".airvpn" : "AirVPN"); if (f.isDirectory()) { return f; } } } catch (PlatformManagerException e) { } String appData; String userhome = System.getProperty("user.home"); if (Constants.isWindows) { appData = SystemProperties.getEnvironmentalVariable("LOCALAPPDATA"); if (appData != null && appData.length() > 0) { } else { appData = userhome + SystemProperties.SEP + "Application Data"; } } else if (Constants.isOSX) { appData = userhome + SystemProperties.SEP + "Library" + SystemProperties.SEP + "Application Support"; } else { // unix type appData = userhome; } File f = new File(appData, Constants.isLinux ? ".airvpn" : "AirVPN"); if (f.isDirectory()) { return f; } return null; } private int ourPortInList(PortInfo[] ports) { 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 != coreUDPPort) { return -1; } boolean skipIfNotOurBinding = ports.length != 20; String sPort = Integer.toString(coreUDPPort); for (int i = 0; i < ports.length; i++) { PortInfo portInfo = ports[i]; if (!portInfo.ourBinding && skipIfNotOurBinding) { return -1; } if (portInfo.port.equals(sPort)) { return i; } } return -1; } 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, "airvpn.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, "airvpn.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; } public static void main(String[] args) { Checker checker = new Checker(); File vpnConfigPath = checker.getVPNConfigPath(); System.out.println(vpnConfigPath); String defaultUsername = checker.getDefaultUsername(); System.out.println("user=" + defaultUsername + "/" + checker.getPassword()); checker.callRPCforPort(null, new StringBuilder()); } private static class PortInfo { String port; boolean ourBinding; public PortInfo(String port, boolean ourBinding) { super(); this.port = port; this.ourBinding = ourBinding; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return port + ";" + ourBinding; } } }