/* * Created on Jan 31, 2008 * Created by Paul Gardner * * Copyright 2008 Vuze, 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; version 2 of the License only. * * 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. * * 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.aelitis.azureus.plugins.net.netstatus.swt; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.Inet6Address; import java.net.InetAddress; import java.net.URL; import java.util.*; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; import java.security.cert.Certificate; import org.gudy.azureus2.core3.util.BDecoder; import org.gudy.azureus2.core3.util.Constants; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.FileUtil; import org.gudy.azureus2.pluginsimpl.local.utils.resourcedownloader.ResourceDownloaderFactoryImpl; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.networkmanager.admin.*; import com.aelitis.azureus.core.proxy.AEProxyFactory; import com.aelitis.azureus.core.proxy.AEProxyFactory.PluginProxy; import com.aelitis.azureus.core.versioncheck.VersionCheckClient; import com.aelitis.azureus.plugins.net.netstatus.NetStatusPlugin; import com.aelitis.azureus.plugins.net.netstatus.NetStatusProtocolTesterBT; import com.aelitis.azureus.plugins.net.netstatus.NetStatusProtocolTesterListener; public class NetStatusPluginTester { //public static final int TEST_PING_ROUTE = 0x00000001; public static final int TEST_NAT_PROXIES = 0x00000002; public static final int TEST_OUTBOUND = 0x00000004; public static final int TEST_INBOUND = 0x00000008; public static final int TEST_BT_CONNECT = 0x00000010; public static final int TEST_IPV6 = 0x00000020; public static final int TEST_VUZE_SERVICES = 0x00000040; public static final int TEST_PROXY_CONNECT = 0x00000080; private static final int ROUTE_TIMEOUT = 120*1000; private NetStatusPlugin plugin; private int test_types; private loggerProvider logger; private volatile boolean test_cancelled; public NetStatusPluginTester( NetStatusPlugin _plugin, int _test_types, loggerProvider _logger ) { plugin = _plugin; test_types = _test_types; logger = _logger; } protected boolean doTest( int type ) { if ( test_cancelled ){ return( false ); } return((test_types & type ) != 0 ); } public void run(AzureusCore core) { final NetworkAdmin admin = NetworkAdmin.getSingleton(); boolean checked_public = false; Set<InetAddress> public_addresses = new HashSet<InetAddress>(); InetAddress def_pa = admin.getDefaultPublicAddress(); if ( def_pa != null ){ log( "Default public address is " + def_pa.getHostAddress()); addPublicAddress( public_addresses, def_pa ); checked_public = true; } /* this ain't working well and some users reporting crashes so boo * if ( doTest( TEST_PING_ROUTE )){ log( "Testing routing for the following interfaces:" ); NetworkAdminNetworkInterface[] interfaces = admin.getInterfaces(); for (int i=0;i<interfaces.length;i++){ NetworkAdminNetworkInterface intf = interfaces[i]; NetworkAdminNetworkInterfaceAddress[] addresses = intf.getAddresses(); String a_str = ""; for (int j=0;j<addresses.length;j++){ NetworkAdminNetworkInterfaceAddress address = addresses[j]; InetAddress ia = address.getAddress(); if ( ia.isLoopbackAddress() || ia instanceof Inet6Address ){ }else{ a_str += (a_str.length()==0?"":",") + ia.getHostAddress(); } } if ( a_str.length() > 0 ){ log( " " + intf.getName() + "/" + intf.getDisplayName() + ": " + a_str ); } } if ( admin.canPing()){ log( "Running ping tests" ); try{ InetAddress target_address = InetAddress.getByName( plugin.getPingTarget()); final Map active_pings = new HashMap(); admin.pingTargets( target_address, ROUTE_TIMEOUT, new NetworkAdminRoutesListener() { private int timeouts; public boolean foundNode( NetworkAdminNetworkInterfaceAddress intf, NetworkAdminNode[] route, int distance, int rtt ) { if ( test_cancelled ){ return( false ); } synchronized( active_pings ){ active_pings.put( intf, route ); } log( " " + intf.getAddress().getHostAddress() + " -> " + route[route.length-1].getAddress().getHostAddress()); return( false ); } public boolean timeout( NetworkAdminNetworkInterfaceAddress intf, NetworkAdminNode[] route, int distance ) { if ( test_cancelled ){ return( false ); } log( " " + intf.getAddress().getHostAddress() + " - timeout" ); timeouts++; if ( timeouts >= 3 ){ return( false ); } return( true ); } }); if ( test_cancelled ){ return; } int num_routes = active_pings.size(); if ( num_routes == 0 ){ logError( "No active pings found!" ); }else{ log( "Found " + num_routes + " pings(s)" ); Iterator it = active_pings.entrySet().iterator(); while( it.hasNext()){ Map.Entry entry = (Map.Entry)it.next(); NetworkAdminNetworkInterfaceAddress address = (NetworkAdminNetworkInterfaceAddress)entry.getKey(); NetworkAdminNode[] route = (NetworkAdminNode[])entry.getValue(); String node_str = ""; for (int i=0;i<route.length;i++){ node_str += (i==0?"":",") + route[i].getAddress().getHostAddress(); } log( " " + address.getInterface().getName() + "/" + address.getAddress().getHostAddress() + " - " + node_str ); } } }catch( Throwable e ){ logError( "Pinging failed: " + Debug.getNestedExceptionMessage(e)); } }else{ logError( "Can't run ping test as not supported" ); } if ( test_cancelled ){ return; } if ( admin.canTraceRoute()){ log( "Running trace route tests" ); try{ InetAddress target_address = InetAddress.getByName( plugin.getPingTarget()); final Map active_routes = new HashMap(); admin.getRoutes( target_address, ROUTE_TIMEOUT, new NetworkAdminRoutesListener() { private String last_as = ""; public boolean foundNode( NetworkAdminNetworkInterfaceAddress intf, NetworkAdminNode[] route, int distance, int rtt ) { if ( test_cancelled ){ return( false ); } synchronized( active_routes ){ active_routes.put( intf, route ); } InetAddress ia = route[route.length-1].getAddress(); String as = ""; if ( !ia.isLinkLocalAddress() && !ia.isSiteLocalAddress()){ try{ NetworkAdminASN asn = admin.lookupASN( ia ); as = asn.getString(); if ( as.equals( last_as )){ as = ""; }else{ last_as = as; } }catch( Throwable e ){ } } log( " " + intf.getAddress().getHostAddress() + " -> " + ia.getHostAddress() + " (hop=" + distance + ")" + (as.length()==0?"":( " - " + as ))); return( true ); } public boolean timeout( NetworkAdminNetworkInterfaceAddress intf, NetworkAdminNode[] route, int distance ) { if ( test_cancelled ){ return( false ); } log( " " + intf.getAddress().getHostAddress() + " - timeout (hop=" + distance + ")" ); // see if we're getting nowhere if ( route.length == 0 && distance >= 5 ){ logError( " giving up, no responses" ); return( false ); } // see if we've got far enough if ( route.length >= 5 && distance > 6 ){ log( " truncating, sufficient responses" ); return( false ); } return( true ); } }); if ( test_cancelled ){ return; } int num_routes = active_routes.size(); if ( num_routes == 0 ){ logError( "No active routes found!" ); }else{ log( "Found " + num_routes + " route(s)" ); Iterator it = active_routes.entrySet().iterator(); while( it.hasNext()){ Map.Entry entry = (Map.Entry)it.next(); NetworkAdminNetworkInterfaceAddress address = (NetworkAdminNetworkInterfaceAddress)entry.getKey(); NetworkAdminNode[] route = (NetworkAdminNode[])entry.getValue(); String node_str = ""; for (int i=0;i<route.length;i++){ node_str += (i==0?"":",") + route[i].getAddress().getHostAddress(); } log( " " + address.getInterface().getName() + "/" + address.getAddress().getHostAddress() + " - " + node_str ); } } }catch( Throwable e ){ logError( "Route tracing failed: " + Debug.getNestedExceptionMessage(e)); } }else{ logError( "Can't run trace route test as not supported" ); } if ( test_cancelled ){ return; } } */ if ( doTest( TEST_NAT_PROXIES )){ checked_public = true; NetworkAdminNATDevice[] nat_devices = admin.getNATDevices(core); log( nat_devices.length + " NAT device" + (nat_devices.length==1?"":"s") + " found" ); for (int i=0;i<nat_devices.length;i++){ NetworkAdminNATDevice device = nat_devices[i]; InetAddress ext_address = device.getExternalAddress(); addPublicAddress( public_addresses, ext_address ); log( " " + device.getString()); } NetworkAdminSocksProxy[] socks_proxies = admin.getSocksProxies(); if ( socks_proxies.length == 0 ){ log( "No SOCKS proxy found" ); }else if ( socks_proxies.length == 1 ){ log( "One SOCKS proxy found" ); }else{ log( socks_proxies.length + " SOCKS proxies found" ); } for (int i=0;i<socks_proxies.length;i++){ NetworkAdminSocksProxy proxy = socks_proxies[i]; log( " " + proxy.getString()); } NetworkAdminHTTPProxy http_proxy = admin.getHTTPProxy(); if ( http_proxy == null ){ log( "No HTTP proxy found" ); }else{ log( "HTTP proxy found" ); log( " " + http_proxy.getString()); } } InetAddress[] bind_addresses = admin.getAllBindAddresses( false ); int num_binds = 0; for ( int i=0;i<bind_addresses.length;i++ ){ if ( bind_addresses[i] != null ){ num_binds++; } } if ( num_binds == 0 ){ log( "No explicit bind address set" ); }else{ log( num_binds + " bind addresses" ); for ( int i=0;i<bind_addresses.length;i++ ){ if ( bind_addresses[i] != null ){ log( " " + bind_addresses[i].getHostAddress()); } } } if ( doTest( TEST_OUTBOUND )){ checked_public = true; NetworkAdminProtocol[] outbound_protocols = admin.getOutboundProtocols(core); if ( outbound_protocols.length == 0 ){ log( "No outbound protocols" ); }else{ for (int i=0;i<outbound_protocols.length;i++){ if ( test_cancelled ){ return; } NetworkAdminProtocol protocol = outbound_protocols[i]; log( "Testing " + protocol.getName()); try{ InetAddress public_address = protocol.test( null, new NetworkAdminProgressListener() { public void reportProgress( String task ) { log( " " + task ); } }); logSuccess( " Test successful" ); addPublicAddress( public_addresses, public_address ); }catch( Throwable e ){ logError( " Test failed", e ); } } } } if ( doTest( TEST_INBOUND )){ checked_public = true; NetworkAdminProtocol[] inbound_protocols = admin.getInboundProtocols(core); if ( inbound_protocols.length == 0 ){ log( "No inbound protocols" ); }else{ for (int i=0;i<inbound_protocols.length;i++){ if ( test_cancelled ){ return; } NetworkAdminProtocol protocol = inbound_protocols[i]; log( "Testing " + protocol.getName()); try{ InetAddress public_address = protocol.test( null, new NetworkAdminProgressListener() { public void reportProgress( String task ) { log( " " + task ); } }); logSuccess( " Test successful" ); addPublicAddress( public_addresses, public_address ); }catch( Throwable e ){ logError( " Test failed", e ); logInfo( " Check your port forwarding for " + protocol.getTypeString() + " " + protocol.getPort()); } } } } if ( checked_public ){ if ( public_addresses.size() == 0 ){ log( "No public addresses found" ); }else{ Iterator<InetAddress> it = public_addresses.iterator(); log( public_addresses.size() + " public/external addresses found" ); while( it.hasNext()){ InetAddress pub_address = it.next(); log( " " + pub_address.getHostAddress()); try{ NetworkAdminASN asn = admin.lookupASN(pub_address); log( " AS details: " + asn.getString()); }catch( Throwable e ){ logError( " failed to lookup AS", e ); } } } } if ( doTest( TEST_VUZE_SERVICES )){ log( "Vuze Services test" ); String[][] services = { { "Vuze Website", "https://www.vuze.com/" }, { "Client Website", "https://client.vuze.com/" }, { "Version Server", "http://version.vuze.com/?dee" }, { "Pairing Server", "https://pair.vuze.com/pairing/web/view?" }, { "License Server", "https://license.vuze.com/licence" }, { "Plugins Website", "https://plugins.vuze.com/" }, }; for ( String[] service: services ){ if ( test_cancelled ){ return; } try{ URL url = new URL( service[1] ); log( " " + service[0] + " - " + url.getHost()); boolean is_https = url.getProtocol().equals( "https" ); if ( is_https ){ String[] host_bits = url.getHost().split( "\\." ); String host_match = "." + host_bits[host_bits.length-2] + "." + host_bits[host_bits.length-1]; HttpsURLConnection con = (HttpsURLConnection)url.openConnection(); con.setHostnameVerifier( new HostnameVerifier() { public boolean verify( String host, SSLSession session ) { return( true ); } }); con.setInstanceFollowRedirects( false ); con.setConnectTimeout( 30*1000 ); con.setReadTimeout( 30*1000 ); con.getResponseCode(); con.getInputStream(); Certificate[] certs = con.getServerCertificates(); if ( certs == null || certs.length == 0 ){ logError( " No certificates returned" ); }else{ Certificate cert = certs[0]; java.security.cert.X509Certificate x509_cert; if ( cert instanceof java.security.cert.X509Certificate ){ x509_cert = (java.security.cert.X509Certificate)cert; }else{ java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509"); x509_cert = (java.security.cert.X509Certificate)cf.generateCertificate(new ByteArrayInputStream(cert.getEncoded())); } log( " Certificate: " + x509_cert.getSubjectDN()); Collection<List<?>> alt_names = x509_cert.getSubjectAlternativeNames(); boolean match = false; for ( List<?> alt_name: alt_names ){ int type = ((Number)alt_name.get(0)).intValue(); if ( type == 2 ){ // DNS name String dns_name = (String)alt_name.get(1); if ( dns_name.endsWith( host_match )){ match = true; break; } } } if ( !match ){ logError( " Failed: Host '" + host_match + "' not found in certificate" ); }else{ logSuccess( " Connection result: " + con.getResponseCode() + "/" + con.getResponseMessage()); } } }else{ HttpURLConnection con = (HttpURLConnection)url.openConnection(); con.setInstanceFollowRedirects( false ); con.setConnectTimeout( 30*1000 ); con.setReadTimeout( 30*1000 ); if ( con.getResponseCode() != 200 ){ throw( new Exception( "Connection failed: " + con.getResponseCode() + "/" + con.getResponseMessage())); } Map resp = BDecoder.decode( new BufferedInputStream( con.getInputStream(), 16*1024 )); if ( resp != null && resp.containsKey( "version" )){ logSuccess( " Connection result: " + con.getResponseCode() + "/" + con.getResponseMessage()); }else{ logError( " Unexpected reply from server: " + resp ); } } }catch( Throwable e ){ logError( " Failed: " + Debug.getNestedExceptionMessage( e )); } } } if ( doTest( TEST_PROXY_CONNECT )){ log( "Indirect Connect test" ); try{ URL target = new URL( "https://www.vuze.com" ); PluginProxy proxy = AEProxyFactory.getPluginProxy( "Network Status test", target ); if ( proxy == null ){ String url_str = "http://azureus.sourceforge.net/plugin_detailssf.php?plugin=aznettor&os="; if ( Constants.isWindows ){ url_str += "Windows"; }else{ url_str += "Mac%20OSX"; } URL url = new URL( url_str ); logError( " No plugin proxy available" ); logInfo( " For the plugin installer see " + url.toExternalForm()); }else{ log( " Connecting to " + target.toExternalForm()); HttpURLConnection con = (HttpURLConnection)proxy.getURL().openConnection( proxy.getProxy()); if ( con instanceof HttpsURLConnection ){ ((HttpsURLConnection)con).setHostnameVerifier( new HostnameVerifier() { public boolean verify( String host, SSLSession session ) { return( true ); } }); } con.setRequestProperty( "HOST", proxy.getURLHostRewrite()); con.setInstanceFollowRedirects( false ); con.setConnectTimeout( 60*1000 ); con.setReadTimeout( 30*1000 ); try{ int resp = con.getResponseCode(); if ( con instanceof HttpsURLConnection ){ Certificate[] certs = ((HttpsURLConnection)con).getServerCertificates(); if ( certs == null || certs.length == 0 ){ logError( " No certificates returned" ); }else{ Certificate cert = certs[0]; java.security.cert.X509Certificate x509_cert; if ( cert instanceof java.security.cert.X509Certificate ){ x509_cert = (java.security.cert.X509Certificate)cert; }else{ java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509"); x509_cert = (java.security.cert.X509Certificate)cf.generateCertificate(new ByteArrayInputStream(cert.getEncoded())); } log( " Certificate: " + x509_cert.getSubjectDN()); } } if ( resp == 200 ){ logSuccess( " Connection result: " + con.getResponseCode() + "/" + con.getResponseMessage()); }else{ log( " Connection result: " + con.getResponseCode() + "/" + con.getResponseMessage()); } }finally{ proxy.setOK( true ); } } }catch( Throwable e ){ logError( " Failed: " + Debug.getNestedExceptionMessage( e )); logError( " Check the logs for the 'Tor Helper Plugin' (Tools->Plugins->Log Views)" ); } } if ( doTest( TEST_BT_CONNECT )){ log( "Distributed protocol test" ); NetStatusProtocolTesterBT bt_test = plugin.getProtocolTester().runTest( new NetStatusProtocolTesterListener() { private List sessions = new ArrayList(); public void complete( NetStatusProtocolTesterBT tester ) { log( "Results", false ); if ( tester.getOutboundConnects() < 4 ){ log( " insufficient outbound connects for analysis", false ); return; } int outgoing_seed_ok = 0; int outgoing_leecher_ok = 0; int outgoing_seed_bad = 0; int outgoing_leecher_bad = 0; int incoming_connect_ok = 0; for (int i=0;i<sessions.size();i++){ NetStatusProtocolTesterBT.Session session = (NetStatusProtocolTesterBT.Session)sessions.get(i); if ( session.isOK()){ if ( session.isInitiator()){ if ( session.isSeed()){ outgoing_seed_ok++; }else{ outgoing_leecher_ok++; } }else{ incoming_connect_ok++; } }else{ if ( session.isConnected()){ if ( session.isInitiator()){ if ( session.isSeed()){ outgoing_seed_bad++; }else{ outgoing_leecher_bad++; } }else{ incoming_connect_ok++; } } } log( " " + ( session.isInitiator()?"Outbound":"Inbound" ) + "," + ( session.isSeed()?"Seed":"Leecher") + "," + session.getProtocolString(), false ); } boolean good = true; if ( incoming_connect_ok == 0 ){ logError( " No incoming connections received, likely NAT problems" ); good = false; } if ( outgoing_leecher_ok > 0 && outgoing_seed_ok == 0 && outgoing_seed_bad > 0 ){ logError( " Outgoing seed connects appear to be failing while non-seeds succeed" ); good = false; } if ( good ){ logSuccess( " Test successful" ); } } public void sessionAdded( NetStatusProtocolTesterBT.Session session ) { synchronized( sessions ){ sessions.add( session ); } } public void log( String str, boolean detailed ) { NetStatusPluginTester.this.log( " " + str, detailed ); } public void logError( String str ) { NetStatusPluginTester.this.logError( " " + str ); } public void logError( String str, Throwable e ) { NetStatusPluginTester.this.logError( " " + str, e ); } }); while( !bt_test.waitForCompletion( 5000 )){ if ( isCancelled()){ bt_test.destroy(); break; } log( " Status: " + bt_test.getStatus()); } } if ( doTest( TEST_IPV6 )){ log( "IPv6 test" ); InetAddress ipv6_address = admin.getDefaultPublicAddressV6(); if ( ipv6_address == null ){ log( " No default public IPv6 address found" ); }else{ log( " Default public IPv6 address: " + ipv6_address.getHostAddress()); log( " Testing connectivity..." ); String res = VersionCheckClient.getSingleton().getExternalIpAddress( false, true, true ); if ( res != null && res.length() > 0 ){ logSuccess( " Connect succeeded, reported IPv6 address: " + res ); }else{ logError( " Connect failed" ); } } } } protected void addPublicAddress( Set<InetAddress> addresses, InetAddress address ) { if ( address == null ){ return; } if ( address.isAnyLocalAddress() || address.isLoopbackAddress() || address.isLinkLocalAddress()|| address.isSiteLocalAddress()){ return; } addresses.add( address ); } public void cancel() { test_cancelled = true; } public boolean isCancelled() { return( test_cancelled ); } protected void log( String str ) { log( str, false ); } protected void log( String str, boolean detailed ) { logger.log( str, detailed ); } protected void logSuccess( String str ) { logger.logSuccess( str ); } protected void logInfo( String str ) { logger.logInfo( str ); } protected void log( String str, Throwable e ) { logger.log( str + ": " + e.getLocalizedMessage(), false ); } protected void logError( String str ) { logger.logFailure( str ); } protected void logError( String str, Throwable e ) { logger.logFailure( str + ": " + e.getLocalizedMessage()); } public interface loggerProvider { public void log( String str, boolean is_detailed ); public void logSuccess( String str ); public void logInfo( String str ); public void logFailure( String str ); } }