/* * Created on 16-Jun-2004 * Created by Paul Gardner * Copyright (C) 2004, 2005, 2006 Aelitis, 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. * 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. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.plugins.upnp; /** * @author parg * */ import java.util.*; import org.gudy.azureus2.core3.config.*; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.AsyncDispatcher; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.RandomUtils; import org.gudy.azureus2.plugins.logging.LoggerChannel; import com.aelitis.azureus.core.util.CopyOnWriteList; import com.aelitis.net.upnp.services.UPnPWANConnection; public class UPnPMappingManager { private static UPnPMappingManager singleton; protected static synchronized UPnPMappingManager getSingleton( UPnPPlugin plugin ) { if ( singleton == null ){ singleton = new UPnPMappingManager( plugin ); } return( singleton ); } private UPnPPlugin plugin; private List<UPnPMapping> mappings = new ArrayList<UPnPMapping>(); private CopyOnWriteList<UPnPMappingManagerListener> listeners = new CopyOnWriteList<UPnPMappingManagerListener>(); private AsyncDispatcher async_dispatcher = new AsyncDispatcher(); protected UPnPMappingManager( UPnPPlugin _plugin ) { plugin = _plugin; // incoming data port // Zyxel routers currently seem to overwrite the TCP mapping for a given port // with the UDP one, leaving the TCP one non-operational. Hack to try setting them // in UDP -> TCP order to hopefully leave the more important one working :) addConfigPort( "upnp.mapping.dataport", false, "UDP.Listen.Port.Enable", "UDP.Listen.Port" ); // this is actually the UDP tracker client mapping, very badly named params... addConfigPort( "upnp.mapping.trackerclientudp", false, "Server Enable UDP", "UDP.NonData.Listen.Port" ); // note that the dht plugin registers its own mapping addConfigPort( "upnp.mapping.dataport", true, "TCP.Listen.Port.Enable", "TCP.Listen.Port" ); addConfigPort( "upnp.mapping.dataport", true, "HTTP.Data.Listen.Port.Enable", "HTTP.Data.Listen.Port" ); // tracker server TCP addConfigPort( "upnp.mapping.tcptrackerport", true, "Tracker Port Enable", "Tracker Port" ); addConfigPortX( "upnp.mapping.tcptrackerport", true, "Tracker Port Enable", "Tracker Port Backups" ); addConfigPort( "upnp.mapping.tcpssltrackerport", true, "Tracker Port SSL Enable", "Tracker Port SSL" ); addConfigPortX( "upnp.mapping.tcpssltrackerport", true, "Tracker Port SSL Enable", "Tracker Port SSL Backups" ); // tracker server UDP addConfigPort( "upnp.mapping.udptrackerport", false, "Tracker Port UDP Enable", "Tracker Port" ); } protected void serviceFound( UPnPWANConnection service ) { boolean save_config = false; if (( service.getCapabilities() & UPnPWANConnection.CAP_UDP_TCP_SAME_PORT ) == 0 ){ // doesn't support UDP and TCP on same port number - patch up // unfortunately some routers remember the stuffed ports and makes them unusable for // either UDP OR TCP until a HARD reset so we need to change both ports... UPnPMapping[] maps = getMappings(); for (int i=0;i<maps.length;i++){ UPnPMapping map = maps[i]; if ( map.isEnabled() && map.isTCP()){ List others = getMappingEx( false, map.getPort()); if ( others.size() == 0 ){ continue; } boolean enabled = false; for (int j=0;j<others.size();j++){ UPnPMapping other = (UPnPMapping)others.get(j); if ( other.isEnabled()){ enabled = true; } } if ( enabled ){ int new_port_1; int new_port_2; while( true ){ int new_port = RandomUtils.generateRandomNetworkListenPort(); if ( getMapping( true, new_port ) == null && getMapping( false, new_port ) == null){ new_port_1 = new_port; break; } } while( true ){ int new_port = RandomUtils.generateRandomNetworkListenPort(); if ( getMapping( true, new_port ) == null && getMapping( false, new_port ) == null){ if ( new_port_1 != new_port ){ new_port_2 = new_port; break; } } } String others_str = ""; for (int j=0;j<others.size();j++){ UPnPMapping other = (UPnPMapping)others.get(j); if ( other.isEnabled()){ others_str += (others_str.length()==0?"":",") + other.getString( new_port_2 ); } } plugin.logAlert( LoggerChannel.LT_WARNING, "upnp.portchange.alert", new String[]{ map.getString( new_port_1 ), String.valueOf( map.getPort()), others_str, String.valueOf( map.getPort())}); map.setPort( new_port_1 ); for (int j=0;j<others.size();j++){ UPnPMapping other = (UPnPMapping)others.get(j); if ( other.isEnabled()){ other.setPort( new_port_2 ); } } save_config = true; } } } } if ( save_config ){ COConfigurationManager.save(); } } protected UPnPMapping addConfigPort( String name_resource, boolean tcp, boolean enabled, final String int_param_name ) { int value = COConfigurationManager.getIntParameter(int_param_name); final UPnPMapping mapping = addMapping( name_resource, tcp, value, enabled ); mapping.addListener( new UPnPMappingListener() { public void mappingChanged( UPnPMapping mapping) { COConfigurationManager.setParameter( int_param_name, mapping.getPort()); } public void mappingDestroyed( UPnPMapping mapping ) { } }); addConfigListener( int_param_name, new ParameterListener() { public void parameterChanged( String name ) { mapping.setPort( COConfigurationManager.getIntParameter(int_param_name)); } }); return( mapping ); } protected void addConfigPort( String name_resource, boolean tcp, final String enabler_param_name, final String int_param_name ) { boolean enabled = COConfigurationManager.getBooleanParameter(enabler_param_name); final UPnPMapping mapping = addConfigPort( name_resource, tcp, enabled, int_param_name ); mapping.addListener( new UPnPMappingListener() { public void mappingChanged( UPnPMapping mapping) { COConfigurationManager.setParameter( int_param_name, mapping.getPort()); } public void mappingDestroyed( UPnPMapping mapping ) { } }); addConfigListener( enabler_param_name, new ParameterListener() { public void parameterChanged( String name ) { mapping.setEnabled( COConfigurationManager.getBooleanParameter(enabler_param_name)); } }); } protected void addConfigPortX( final String name_resource, final boolean tcp, final String enabler_param_name, final String string_param_name ) { final List config_mappings = new ArrayList(); ParameterListener l1 = new ParameterListener() { public void parameterChanged( String name ) { boolean enabled = COConfigurationManager.getBooleanParameter(enabler_param_name); List ports = stringToPorts( COConfigurationManager.getStringParameter( string_param_name )); for (int i=0;i<ports.size();i++){ int port = ((Integer)ports.get(i)).intValue(); if ( config_mappings.size() <= i ){ UPnPMapping mapping = addMapping( name_resource, tcp, port, enabled ); mapping.setEnabled( enabled ); config_mappings.add( mapping ); }else{ ((UPnPMapping)config_mappings.get(i)).setPort( port ); } } for (int i=ports.size();i<config_mappings.size();i++){ ((UPnPMapping)config_mappings.get(i)).setEnabled( false ); } } }; addConfigListener( string_param_name, l1 ); ParameterListener l2 = new ParameterListener() { public void parameterChanged( String name ) { List ports = stringToPorts( COConfigurationManager.getStringParameter( string_param_name )); boolean enabled = COConfigurationManager.getBooleanParameter(enabler_param_name); for (int i=0;i<(enabled?ports.size():config_mappings.size());i++){ ((UPnPMapping)config_mappings.get(i)).setEnabled( enabled ); } } }; addConfigListener( enabler_param_name, l2 ); l1.parameterChanged( null ); l2.parameterChanged( null ); } protected List stringToPorts( String str ) { str = str.replace(',', ';' ); StringTokenizer tok = new StringTokenizer( str, ";" ); List res = new ArrayList(); while( tok.hasMoreTokens()){ try{ res.add( new Integer( tok.nextToken().trim())); }catch( Throwable e ){ Debug.out("Invalid port entry in '" + str + "'", e); } } return( res ); } public UPnPMapping addMapping( String desc_resource, boolean tcp, int port, boolean enabled ) { // System.out.println( "UPnPMappingManager: added '" + desc_resource + "'" + (tcp?"TCP":"UDP") + "/" + port + ", enabled = " + enabled ); UPnPMapping mapping = new UPnPMapping(desc_resource, tcp, port, enabled ); synchronized( mappings ){ mappings.add( mapping ); } added( mapping ); return( mapping ); } public UPnPMapping[] getMappings() { synchronized( mappings ){ UPnPMapping[] res = new UPnPMapping[mappings.size()]; mappings.toArray( res ); return( res ); } } public UPnPMapping getMapping( boolean tcp, int port ) { synchronized( mappings ){ for (int i=0;i<mappings.size();i++){ UPnPMapping mapping = (UPnPMapping)mappings.get(i); if ( mapping.isTCP() == tcp && mapping.getPort() == port ){ return( mapping ); } } } return( null ); } public List getMappingEx( boolean tcp, int port ) { List res = new ArrayList(); synchronized( mappings ){ for (int i=0;i<mappings.size();i++){ UPnPMapping mapping = (UPnPMapping)mappings.get(i); if ( mapping.isTCP() == tcp && mapping.getPort() == port ){ res.add( mapping ); } } } return( res ); } protected void added( UPnPMapping mapping ) { mapping.addListener( new UPnPMappingListener() { public void mappingChanged( UPnPMapping mapping ) { } public void mappingDestroyed( UPnPMapping mapping ) { synchronized( mappings ){ mappings.remove( mapping ); } } }); for (UPnPMappingManagerListener listener: listeners){ try{ listener.mappingAdded( mapping ); }catch( Throwable e ){ Debug.out( e ); } } } public void addListener( UPnPMappingManagerListener l ) { listeners.add(l); } public void removeListener( UPnPMappingManagerListener l ) { listeners.remove(l); } protected void addConfigListener( final String param, final ParameterListener listener ) { COConfigurationManager.addParameterListener( param, new ParameterListener() { public void parameterChanged( String name ) { async_dispatcher.dispatch( new AERunnable() { public void runSupport() { listener.parameterChanged( param ); } }); } }); } }