1 /* 2 * Created on Dec 17, 2013 3 * Created by Paul Gardner 4 * 5 * Copyright 2013 Azureus Software, Inc. All rights reserved. 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or (at your option) any later version. 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 */ 19 20 21 package com.aelitis.azureus.core.proxy.impl; 22 23 import java.lang.ref.WeakReference; 24 import java.net.InetSocketAddress; 25 import java.net.Proxy; 26 import java.net.URL; 27 import java.util.*; 28 29 import org.gudy.azureus2.core3.config.COConfigurationManager; 30 import org.gudy.azureus2.core3.config.ParameterListener; 31 import org.gudy.azureus2.core3.util.AENetworkClassifier; 32 import org.gudy.azureus2.core3.util.AESemaphore; 33 import org.gudy.azureus2.core3.util.Debug; 34 import org.gudy.azureus2.core3.util.SystemTime; 35 import org.gudy.azureus2.core3.util.TorrentUtils; 36 import org.gudy.azureus2.core3.util.UrlUtils; 37 import org.gudy.azureus2.plugins.PluginAdapter; 38 import org.gudy.azureus2.plugins.PluginEvent; 39 import org.gudy.azureus2.plugins.PluginEventListener; 40 import org.gudy.azureus2.plugins.PluginInterface; 41 import org.gudy.azureus2.plugins.ipc.IPCInterface; 42 import org.gudy.azureus2.pluginsimpl.local.PluginInitializer; 43 44 import com.aelitis.azureus.core.AzureusCore; 45 import com.aelitis.azureus.core.AzureusCoreFactory; 46 import com.aelitis.azureus.core.proxy.AEProxySelectorFactory; 47 import com.aelitis.azureus.core.proxy.AEProxyFactory.PluginHTTPProxy; 48 import com.aelitis.azureus.core.proxy.AEProxyFactory.PluginProxy; 49 import com.aelitis.azureus.core.util.CopyOnWriteList; 50 import com.aelitis.azureus.plugins.dht.DHTPluginInterface; 51 52 public class 53 AEPluginProxyHandler 54 { 55 private static CopyOnWriteList<PluginInterface> plugins = new CopyOnWriteList<PluginInterface>(); 56 57 private static final int plugin_init_max_wait = 30*1000; 58 private static final AESemaphore plugin_init_complete = new AESemaphore( "init:waiter" ); 59 60 private static boolean enable_plugin_proxies_with_socks; 61 62 static{ 63 try{ 64 COConfigurationManager.addAndFireParameterListener( 65 "Proxy.SOCKS.disable.plugin.proxies", 66 new ParameterListener() { 67 public void 68 parameterChanged( 69 String parameterName) 70 { 71 enable_plugin_proxies_with_socks = !COConfigurationManager.getBooleanParameter( parameterName ); 72 } 73 }); 74 75 AzureusCore core = AzureusCoreFactory.getSingleton(); 76 77 PluginInterface default_pi = core.getPluginManager().getDefaultPluginInterface(); 78 default_pi.addEventListener( new PluginEventListener() { public void handleEvent( PluginEvent ev ) { int type = ev.getType(); if ( type == PluginEvent.PEV_PLUGIN_OPERATIONAL ){ pluginAdded((PluginInterface)ev.getValue()); } if ( type == PluginEvent.PEV_PLUGIN_NOT_OPERATIONAL ){ pluginRemoved((PluginInterface)ev.getValue()); } } })79 default_pi.addEventListener( 80 new PluginEventListener() 81 { 82 public void 83 handleEvent( 84 PluginEvent ev ) 85 { 86 int type = ev.getType(); 87 88 if ( type == PluginEvent.PEV_PLUGIN_OPERATIONAL ){ 89 90 pluginAdded((PluginInterface)ev.getValue()); 91 } 92 if ( type == PluginEvent.PEV_PLUGIN_NOT_OPERATIONAL ){ 93 94 pluginRemoved((PluginInterface)ev.getValue()); 95 } 96 } 97 }); 98 99 PluginInterface[] plugins = default_pi.getPluginManager().getPlugins( true ); 100 101 for ( PluginInterface pi: plugins ){ 102 103 if ( pi.getPluginState().isOperational()){ 104 105 pluginAdded( pi ); 106 } 107 } 108 default_pi.addListener( new PluginAdapter() { public void initializationComplete() { plugin_init_complete.releaseForever(); } })109 default_pi.addListener( 110 new PluginAdapter() 111 { 112 public void 113 initializationComplete() 114 { 115 plugin_init_complete.releaseForever(); 116 } 117 }); 118 119 }catch( Throwable e ){ 120 121 e.printStackTrace(); 122 } 123 } 124 125 private static void pluginAdded( PluginInterface pi )126 pluginAdded( 127 PluginInterface pi ) 128 { 129 String pid = pi.getPluginID(); 130 131 if ( pid.equals( "aznettor" ) || pid.equals( "azneti2phelper" )){ 132 133 plugins.add( pi ); 134 } 135 } 136 137 private static void pluginRemoved( PluginInterface pi )138 pluginRemoved( 139 PluginInterface pi ) 140 { 141 String pid = pi.getPluginID(); 142 143 if ( pid.equals( "aznettor" ) || pid.equals( "azneti2phelper" )){ 144 145 plugins.remove( pi ); 146 } 147 } 148 149 private static boolean waitForPlugins( int max_wait )150 waitForPlugins( 151 int max_wait ) 152 { 153 if ( PluginInitializer.isInitThread()){ 154 155 Debug.out( "Hmm, rework this" ); 156 } 157 158 return( plugin_init_complete.reserve( max_wait )); 159 } 160 161 private static final Map<Proxy,WeakReference<PluginProxyImpl>> proxy_map = new IdentityHashMap<Proxy,WeakReference<PluginProxyImpl>>(); 162 163 public static boolean hasPluginProxyForNetwork( String network, boolean supports_data )164 hasPluginProxyForNetwork( 165 String network, 166 boolean supports_data ) 167 { 168 long start = SystemTime.getMonotonousTime(); 169 170 while( true ){ 171 172 long rem = plugin_init_max_wait - ( SystemTime.getMonotonousTime() - start ); 173 174 if ( rem <= 0 ){ 175 176 return( false ); 177 } 178 179 boolean wait_complete = waitForPlugins( Math.min( (int)rem, 1000 )); 180 181 boolean result = getPluginProxyForNetwork( network, supports_data ) != null; 182 183 if ( result || wait_complete ){ 184 185 return( result ); 186 } 187 } 188 } 189 190 private static PluginInterface getPluginProxyForNetwork( String network, boolean supports_data )191 getPluginProxyForNetwork( 192 String network, 193 boolean supports_data ) 194 { 195 for ( PluginInterface pi: plugins ){ 196 197 String pid = pi.getPluginID(); 198 199 if ( pid.equals( "aznettor" ) && network == AENetworkClassifier.AT_TOR ){ 200 201 if ( !supports_data ){ 202 203 return( pi ); 204 } 205 } 206 207 if ( pid.equals( "azneti2phelper" ) && network == AENetworkClassifier.AT_I2P ){ 208 209 return( pi ); 210 } 211 } 212 213 return( null ); 214 } 215 216 public static boolean hasPluginProxy()217 hasPluginProxy() 218 { 219 waitForPlugins( plugin_init_max_wait ); 220 221 for ( PluginInterface pi: plugins ){ 222 223 try{ 224 IPCInterface ipc = pi.getIPC(); 225 226 if ( ipc.canInvoke( "testHTTPPseudoProxy", new Object[]{ TorrentUtils.getDecentralisedEmptyURL() })){ 227 228 return( true ); 229 } 230 }catch( Throwable e ){ 231 } 232 } 233 234 return( false ); 235 } 236 237 private static boolean isEnabled()238 isEnabled() 239 { 240 Proxy system_proxy = AEProxySelectorFactory.getSelector().getActiveProxy(); 241 242 if ( system_proxy == null || system_proxy.equals( Proxy.NO_PROXY )){ 243 244 return( true ); 245 246 }else{ 247 248 return( enable_plugin_proxies_with_socks ); 249 } 250 } 251 252 /** 253 * This method should NOT BE CALLED as it is in the .impl package - unfortunately the featman plugin calls it - will be removed 254 * when aefeatman 1.3.2 is released 255 * @param reason 256 * @param target 257 * @deprecated 258 * @return 259 */ 260 261 public static PluginProxyImpl getPluginProxy( String reason, URL target )262 getPluginProxy( 263 String reason, 264 URL target ) 265 { 266 return( getPluginProxy( reason, target, null, false )); 267 } 268 269 public static PluginProxyImpl getPluginProxy( String reason, URL target, Map<String,Object> properties, boolean can_wait )270 getPluginProxy( 271 String reason, 272 URL target, 273 Map<String,Object> properties, 274 boolean can_wait ) 275 { 276 if ( isEnabled()){ 277 278 String url_protocol = target.getProtocol().toLowerCase(); 279 280 if ( url_protocol.startsWith( "http" ) || url_protocol.equals( "ftp" )){ 281 282 if ( can_wait ){ 283 284 waitForPlugins(0); 285 } 286 287 if ( properties == null ){ 288 289 properties = new HashMap<String, Object>(); 290 } 291 292 for ( PluginInterface pi: plugins ){ 293 294 try{ 295 IPCInterface ipc = pi.getIPC(); 296 297 Object[] proxy_details; 298 299 if ( ipc.canInvoke( "getProxy", new Object[]{ reason, target, properties } )){ 300 301 proxy_details = (Object[])ipc.invoke( "getProxy", new Object[]{ reason, target, properties } ); 302 303 }else{ 304 305 proxy_details = (Object[])ipc.invoke( "getProxy", new Object[]{ reason, target } ); 306 } 307 308 if ( proxy_details != null ){ 309 310 if ( proxy_details.length == 2 ){ 311 312 // support old plugins 313 314 proxy_details = new Object[]{ proxy_details[0], proxy_details[1], target.getHost()}; 315 } 316 317 return( new PluginProxyImpl( target.toExternalForm(), reason, ipc, properties, proxy_details )); 318 } 319 }catch( Throwable e ){ 320 } 321 } 322 } 323 } 324 325 return( null ); 326 } 327 328 public static PluginProxyImpl getPluginProxy( String reason, String host, int port, Map<String,Object> properties )329 getPluginProxy( 330 String reason, 331 String host, 332 int port, 333 Map<String,Object> properties ) 334 { 335 if ( isEnabled()){ 336 337 if ( properties == null ){ 338 339 properties = new HashMap<String, Object>(); 340 } 341 342 for ( PluginInterface pi: plugins ){ 343 344 try{ 345 IPCInterface ipc = pi.getIPC(); 346 347 Object[] proxy_details; 348 349 if ( ipc.canInvoke( "getProxy", new Object[]{ reason, host, port, properties })){ 350 351 proxy_details = (Object[])ipc.invoke( "getProxy", new Object[]{ reason, host, port, properties }); 352 353 }else{ 354 355 proxy_details = (Object[])ipc.invoke( "getProxy", new Object[]{ reason, host, port }); 356 } 357 358 if ( proxy_details != null ){ 359 360 return( new PluginProxyImpl( host + ":" + port, reason, ipc, properties, proxy_details )); 361 } 362 }catch( Throwable e ){ 363 } 364 } 365 } 366 367 return( null ); 368 } 369 370 public static PluginProxy getPluginProxy( Proxy proxy )371 getPluginProxy( 372 Proxy proxy ) 373 { 374 if ( proxy != null ){ 375 376 synchronized( proxy_map ){ 377 378 WeakReference<PluginProxyImpl> ref = proxy_map.get( proxy ); 379 380 if ( ref != null ){ 381 382 return( ref.get()); 383 } 384 } 385 } 386 387 return( null ); 388 } 389 390 public static Boolean testPluginHTTPProxy( URL url, boolean can_wait )391 testPluginHTTPProxy( 392 URL url, 393 boolean can_wait ) 394 { 395 if ( isEnabled()){ 396 397 String url_protocol = url.getProtocol().toLowerCase(); 398 399 if ( url_protocol.startsWith( "http" )){ 400 401 if ( can_wait ){ 402 403 waitForPlugins(0); 404 } 405 406 for ( PluginInterface pi: plugins ){ 407 408 try{ 409 IPCInterface ipc = pi.getIPC(); 410 411 return((Boolean)ipc.invoke( "testHTTPPseudoProxy", new Object[]{ url })); 412 413 }catch( Throwable e ){ 414 } 415 } 416 }else{ 417 418 Debug.out( "Unsupported protocol: " + url_protocol ); 419 } 420 } 421 422 return( null ); 423 } 424 425 public static PluginHTTPProxyImpl getPluginHTTPProxy( String reason, URL url, boolean can_wait )426 getPluginHTTPProxy( 427 String reason, 428 URL url, 429 boolean can_wait ) 430 { 431 if ( isEnabled()){ 432 433 String url_protocol = url.getProtocol().toLowerCase(); 434 435 if ( url_protocol.startsWith( "http" )){ 436 437 if ( can_wait ){ 438 439 waitForPlugins(0); 440 } 441 442 for ( PluginInterface pi: plugins ){ 443 444 try{ 445 IPCInterface ipc = pi.getIPC(); 446 447 Proxy proxy = (Proxy)ipc.invoke( "createHTTPPseudoProxy", new Object[]{ reason, url }); 448 449 if ( proxy != null ){ 450 451 return( new PluginHTTPProxyImpl( reason, ipc, proxy )); 452 } 453 }catch( Throwable e ){ 454 } 455 } 456 }else{ 457 458 Debug.out( "Unsupported protocol: " + url_protocol ); 459 } 460 } 461 462 return( null ); 463 } 464 465 public static List<PluginInterface> getPluginHTTPProxyProviders( boolean can_wait )466 getPluginHTTPProxyProviders( 467 boolean can_wait ) 468 { 469 if ( can_wait ){ 470 471 waitForPlugins(0); 472 } 473 474 List<PluginInterface> pis = 475 AzureusCoreFactory.getSingleton().getPluginManager().getPluginsWithMethod( 476 "createHTTPPseudoProxy", 477 new Class[]{ String.class, URL.class }); 478 479 return( pis ); 480 } 481 482 public static Map<String,Object> getPluginServerProxy( String reason, String network, String server_uid, Map<String,Object> options )483 getPluginServerProxy( 484 String reason, 485 String network, 486 String server_uid, 487 Map<String,Object> options ) 488 { 489 waitForPlugins( plugin_init_max_wait ); 490 491 PluginInterface pi = getPluginProxyForNetwork( network, false ); 492 493 if ( pi == null ){ 494 495 return( null ); 496 } 497 498 options = new HashMap<String,Object>( options ); 499 500 options.put( "id", server_uid ); 501 502 try{ 503 IPCInterface ipc = pi.getIPC(); 504 505 Map<String,Object> reply = (Map<String,Object>)ipc.invoke( "getProxyServer", new Object[]{ reason, options }); 506 507 return( reply ); 508 509 }catch( Throwable e ){ 510 511 } 512 513 return( null ); 514 } 515 516 public static DHTPluginInterface getPluginDHTProxy( String reason, String network, Map<String,Object> options )517 getPluginDHTProxy( 518 String reason, 519 String network, 520 Map<String,Object> options ) 521 { 522 waitForPlugins( plugin_init_max_wait ); 523 524 PluginInterface pi = getPluginProxyForNetwork( network, false ); 525 526 if ( pi == null ){ 527 528 return( null ); 529 } 530 531 try{ 532 IPCInterface ipc = pi.getIPC(); 533 534 DHTPluginInterface reply = (DHTPluginInterface)ipc.invoke( "getProxyDHT", new Object[]{ reason, options }); 535 536 return( reply ); 537 538 }catch( Throwable e ){ 539 540 } 541 542 return( null ); 543 } 544 545 private static class 546 PluginProxyImpl 547 implements PluginProxy 548 { 549 private final long create_time = SystemTime.getMonotonousTime(); 550 551 private final String target; 552 private final String reason; 553 554 private final IPCInterface ipc; 555 private final Map<String,Object> proxy_options; 556 private final Object[] proxy_details; 557 558 private List<PluginProxyImpl> children = new ArrayList<AEPluginProxyHandler.PluginProxyImpl>(); 559 560 private PluginProxyImpl( String _target, String _reason, IPCInterface _ipc, Map<String,Object> _proxy_options, Object[] _proxy_details )561 PluginProxyImpl( 562 String _target, 563 String _reason, 564 IPCInterface _ipc, 565 Map<String,Object> _proxy_options, 566 Object[] _proxy_details ) 567 { 568 target = _target; 569 reason = _reason; 570 ipc = _ipc; 571 proxy_options = _proxy_options; 572 proxy_details = _proxy_details; 573 574 WeakReference<PluginProxyImpl> my_ref = new WeakReference<PluginProxyImpl>( this ); 575 576 List<PluginProxyImpl> removed = new ArrayList<PluginProxyImpl>(); 577 578 synchronized( proxy_map ){ 579 580 proxy_map.put( getProxy(), my_ref ); 581 582 if ( proxy_map.size() > 1024 ){ 583 584 long now = SystemTime.getMonotonousTime(); 585 586 Iterator<WeakReference<PluginProxyImpl>> it = proxy_map.values().iterator(); 587 588 while( it.hasNext()){ 589 590 WeakReference<PluginProxyImpl> ref = it.next(); 591 592 PluginProxyImpl pp = ref.get(); 593 594 if ( pp == null ){ 595 596 it.remove(); 597 598 }else{ 599 600 if ( now - pp.create_time > 5*60*1000 ){ 601 602 removed.add( pp ); 603 604 it.remove(); 605 } 606 } 607 } 608 } 609 } 610 611 for ( PluginProxyImpl pp: removed ){ 612 613 pp.setOK( false ); 614 } 615 } 616 617 public String getTarget()618 getTarget() 619 { 620 return( target ); 621 } 622 623 public PluginProxy getChildProxy( String child_reason, URL url)624 getChildProxy( 625 String child_reason, 626 URL url) 627 { 628 PluginProxyImpl child = getPluginProxy( reason + " - " + child_reason, url, proxy_options, false ); 629 630 if ( child != null ){ 631 632 synchronized( children ){ 633 634 children.add( child ); 635 } 636 } 637 638 return( child ); 639 } 640 641 public Proxy getProxy()642 getProxy() 643 { 644 return((Proxy)proxy_details[0]); 645 } 646 647 // URL methods 648 649 public URL getURL()650 getURL() 651 { 652 return((URL)proxy_details[1]); 653 } 654 655 public String getURLHostRewrite()656 getURLHostRewrite() 657 { 658 return((String)proxy_details[2]); 659 } 660 661 // host:port methods 662 663 public String getHost()664 getHost() 665 { 666 return((String)proxy_details[1]); 667 } 668 669 public int getPort()670 getPort() 671 { 672 return((Integer)proxy_details[2]); 673 } 674 675 public void setOK( boolean good )676 setOK( 677 boolean good ) 678 { 679 try{ 680 ipc.invoke( "setProxyStatus", new Object[]{ proxy_details[0], good }); 681 682 }catch( Throwable e ){ 683 } 684 685 List<PluginProxyImpl> kids; 686 687 synchronized( children ){ 688 689 kids = new ArrayList<PluginProxyImpl>( children ); 690 691 children.clear(); 692 } 693 694 for ( PluginProxyImpl child: kids ){ 695 696 child.setOK( good ); 697 } 698 699 synchronized( proxy_map ){ 700 701 proxy_map.remove( getProxy()); 702 } 703 } 704 } 705 706 private static class 707 PluginHTTPProxyImpl 708 implements PluginHTTPProxy 709 { 710 private String reason; 711 private IPCInterface ipc; 712 private Proxy proxy; 713 714 private PluginHTTPProxyImpl( String _reason, IPCInterface _ipc, Proxy _proxy )715 PluginHTTPProxyImpl( 716 String _reason, 717 IPCInterface _ipc, 718 Proxy _proxy ) 719 { 720 reason = _reason; 721 ipc = _ipc; 722 proxy = _proxy; 723 } 724 725 public Proxy getProxy()726 getProxy() 727 { 728 return( proxy ); 729 } 730 731 public String proxifyURL( String url )732 proxifyURL( 733 String url ) 734 { 735 try{ 736 URL _url = new URL( url ); 737 738 InetSocketAddress pa = (InetSocketAddress)proxy.address(); 739 740 _url = UrlUtils.setHost( _url, pa.getAddress().getHostAddress()); 741 _url = UrlUtils.setPort( _url, pa.getPort()); 742 743 url = _url.toExternalForm(); 744 745 url += ( url.indexOf('?')==-1?"?":"&" ) + "_azpproxy=1"; 746 747 return( url ); 748 749 }catch( Throwable e ){ 750 751 Debug.out( "Failed to proxify URL: " + url, e ); 752 753 return( url ); 754 } 755 } 756 757 public void destroy()758 destroy() 759 { 760 try{ 761 762 ipc.invoke( "destroyHTTPPseudoProxy", new Object[]{ proxy }); 763 764 }catch( Throwable e ){ 765 766 Debug.out( e ); 767 } 768 } 769 } 770 } 771