/* * Created on 15-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.net.upnp.impl.services; import java.util.*; import org.gudy.azureus2.core3.util.*; import com.aelitis.net.upnp.*; import com.aelitis.net.upnp.impl.UPnPImpl; import com.aelitis.net.upnp.impl.device.UPnPRootDeviceImpl; import com.aelitis.net.upnp.services.UPnPWANConnection; import com.aelitis.net.upnp.services.UPnPWANConnectionListener; import com.aelitis.net.upnp.services.UPnPWANConnectionPortMapping; /** * @author parg * */ public abstract class UPnPSSWANConnectionImpl implements UPnPWANConnection { private static AEMonitor class_mon = new AEMonitor( "UPnPSSWANConnection" ); private static List services = new ArrayList(); static{ SimpleTimer.addPeriodicEvent( "UPnPSSWAN:checker", 10*60*1000, new TimerEventPerformer() { public void perform( TimerEvent ev ) { try{ List to_check = new ArrayList(); try{ class_mon.enter(); Iterator it = services.iterator(); while( it.hasNext()){ UPnPSSWANConnectionImpl s = (UPnPSSWANConnectionImpl)it.next(); if ( s.getGenericService().getDevice().getRootDevice().isDestroyed()){ it.remove(); }else{ to_check.add( s ); } } }finally{ class_mon.exit(); } for (int i=0;i<to_check.size();i++){ try{ ((UPnPSSWANConnectionImpl)to_check.get(i)).checkMappings(); }catch( Throwable e ){ //Debug.printStackTrace(e); } } }catch( Throwable e ){ Debug.printStackTrace(e); } } } ); } private UPnPServiceImpl service; private List mappings = new ArrayList(); private List listeners = new ArrayList(); private boolean recheck_mappings = true; // start off true to avoid logging first of repetitive failures private boolean last_mapping_check_failed = true; protected UPnPSSWANConnectionImpl( UPnPServiceImpl _service ) { service = _service; try{ class_mon.enter(); services.add( this ); }finally{ class_mon.exit(); } } public int getCapabilities() { String device_name = service.getDevice().getRootDevice().getDevice().getFriendlyName(); int capabilities = CAP_ALL; if ( device_name.equals( "WRT54G" )){ capabilities = CAP_ALL & ~CAP_UDP_TCP_SAME_PORT; } return( capabilities ); } public UPnPService getGenericService() { return( service ); } public String[] getStatusInfo() throws UPnPException { UPnPAction act = service.getAction( "GetStatusInfo" ); if ( act == null ){ log( "Action 'GetStatusInfo' not supported, binding not established" ); throw( new UPnPException( "GetStatusInfo not supported" )); }else{ UPnPActionInvocation inv = act.getInvocation(); UPnPActionArgument[] args = inv.invoke(); String connection_status = null; String connection_error = null; String uptime = null; for (int i=0;i<args.length;i++){ UPnPActionArgument arg = args[i]; String name = arg.getName(); if ( name.equalsIgnoreCase("NewConnectionStatus")){ connection_status = arg.getValue(); }else if ( name.equalsIgnoreCase("NewLastConnectionError")){ connection_error = arg.getValue(); }else if ( name.equalsIgnoreCase("NewUptime")){ uptime = arg.getValue(); } } return( new String[]{ connection_status, connection_error, uptime }); } } public void periodicallyRecheckMappings( boolean on ) { recheck_mappings = on; } protected void checkMappings() throws UPnPException { if ( !recheck_mappings ){ return; } List mappings_copy; try{ class_mon.enter(); mappings_copy = new ArrayList( mappings ); }finally{ class_mon.exit(); } UPnPWANConnectionPortMapping[] current = getPortMappings(); Iterator it = mappings_copy.iterator(); while( it.hasNext()){ portMapping mapping = (portMapping)it.next(); for (int j=0;j<current.length;j++){ UPnPWANConnectionPortMapping c = current[j]; if ( c.getExternalPort() == mapping.getExternalPort() && c.isTCP() == mapping.isTCP()){ it.remove(); break; } } } boolean log = false; if ( mappings_copy.size() > 0 ){ if ( !last_mapping_check_failed ){ last_mapping_check_failed = true; log = true; } }else{ last_mapping_check_failed = false; } it = mappings_copy.iterator(); while( it.hasNext()){ portMapping mapping = (portMapping)it.next(); try{ // some routers appear to continually fail to report the mappings - avoid // reporting this if ( log ){ log( "Re-establishing mapping " + mapping.getString()); } addPortMapping( mapping.isTCP(), mapping.getExternalPort(), mapping.getDescription()); }catch( Throwable e ){ Debug.printStackTrace(e); } } } public void addPortMapping( boolean tcp, // false -> UDP int port, String description ) throws UPnPException { UPnPAction act = service.getAction( "AddPortMapping" ); if ( act == null ){ log( "Action 'AddPortMapping' not supported, binding not established" ); }else{ UPnPActionInvocation add_inv = act.getInvocation(); add_inv.addArgument( "NewRemoteHost", "" ); // "" = wildcard for hosts, 0 = wildcard for ports add_inv.addArgument( "NewExternalPort", "" + port ); add_inv.addArgument( "NewProtocol", tcp?"TCP":"UDP" ); add_inv.addArgument( "NewInternalPort", "" + port ); add_inv.addArgument( "NewInternalClient", service.getDevice().getRootDevice().getLocalAddress().getHostAddress()); add_inv.addArgument( "NewEnabled", "1" ); add_inv.addArgument( "NewPortMappingDescription", description ); add_inv.addArgument( "NewLeaseDuration", "0" ); // 0 -> infinite (?) boolean ok = false; try{ add_inv.invoke(); ok = true; }catch( UPnPException original_error ){ // some routers won't add properly if the mapping's already there try{ log("Problem when adding port mapping - will try to see if an existing mapping is in the way"); deletePortMapping(tcp, port); }catch( Throwable e ){ throw( original_error ); } add_inv.invoke(); ok = true; }finally{ ((UPnPRootDeviceImpl)service.getDevice().getRootDevice()).portMappingResult(ok); for (int i=0;i<listeners.size();i++){ UPnPWANConnectionListener listener = (UPnPWANConnectionListener)listeners.get(i); try{ listener.mappingResult( this, ok ); }catch( Throwable e){ Debug.printStackTrace(e); } } } try{ class_mon.enter(); Iterator it = mappings.iterator(); while( it.hasNext()){ portMapping m = (portMapping)it.next(); if ( m.getExternalPort() == port && m.isTCP() == tcp ){ it.remove(); } } mappings.add( new portMapping( port, tcp, "", description )); }finally{ class_mon.exit(); } } } public void deletePortMapping( boolean tcp, int port ) throws UPnPException { UPnPAction act = service.getAction( "DeletePortMapping" ); if ( act == null ){ log( "Action 'DeletePortMapping' not supported, binding not removed" ); }else{ boolean mapping_found = false; try{ class_mon.enter(); Iterator it = mappings.iterator(); while( it.hasNext()){ portMapping mapping = (portMapping)it.next(); if ( mapping.getExternalPort() == port && mapping.isTCP() == tcp ){ it.remove(); mapping_found = true; break; } } }finally{ class_mon.exit(); } try{ long start = SystemTime.getCurrentTime(); UPnPActionInvocation inv = act.getInvocation(); inv.addArgument( "NewRemoteHost", "" ); // "" = wildcard for hosts, 0 = wildcard for ports inv.addArgument( "NewProtocol", tcp?"TCP":"UDP" ); inv.addArgument( "NewExternalPort", "" + port ); inv.invoke(); long elapsed = SystemTime.getCurrentTime() - start; if ( elapsed > 4000 ){ String info = service.getDevice().getRootDevice().getInfo(); ((UPnPImpl)service.getDevice().getRootDevice().getUPnP()).logAlert( "UPnP device '" + info + "' is taking a long time to release port mappings, consider disabling this via the UPnP configuration.", false, UPnPLogListener.TYPE_ONCE_EVER ); } }catch( UPnPException e ){ // only bitch about the failure if we believed we mapped it in the first place if ( mapping_found ){ throw( e ); }else{ log( "Removal of mapping failed but not established explicitly so ignoring error" ); } } } } public UPnPWANConnectionPortMapping[] getPortMappings() throws UPnPException { boolean ok = true; try{ //UPnPStateVariable noe = service.getStateVariable("PortMappingNumberOfEntries"); //System.out.println( "NOE = " + noe.getValue()); int entries = 0; //Integer.parseInt( noe.getValue()); // some routers (e.g. Gudy's) return 0 here whatever! // In this case take mindless approach // hmm, even for my router the state variable isn't accurate... UPnPAction act = service.getAction( "GetGenericPortMappingEntry" ); if ( act == null ){ log( "Action 'GetGenericPortMappingEntry' not supported, can't enumerate bindings" ); return( new UPnPWANConnectionPortMapping[0] ); }else{ List res = new ArrayList(); // I've also seen some routers loop here rather than failing when the index gets too large (they // seem to keep returning the last entry) - check for a duplicate entry and exit if found portMapping prev_mapping = null; for (int i=0;i<(entries==0?512:entries);i++){ UPnPActionInvocation inv = act.getInvocation(); inv.addArgument( "NewPortMappingIndex", "" + i ); try{ UPnPActionArgument[] outs = inv.invoke(); int port = 0; boolean tcp = false; String internal_host = null; String description = ""; for (int j=0;j<outs.length;j++){ UPnPActionArgument out = outs[j]; String out_name = out.getName(); if ( out_name.equalsIgnoreCase("NewExternalPort")){ port = Integer.parseInt( out.getValue()); }else if ( out_name.equalsIgnoreCase( "NewProtocol" )){ tcp = out.getValue().equalsIgnoreCase("TCP"); }else if ( out_name.equalsIgnoreCase( "NewInternalClient" )){ internal_host = out.getValue(); }else if ( out_name.equalsIgnoreCase( "NewPortMappingDescription" )){ description = out.getValue(); } } if ( prev_mapping != null ){ if ( prev_mapping.getExternalPort() == port && prev_mapping.isTCP() == tcp ){ // repeat, get out break; } } prev_mapping = new portMapping( port, tcp, internal_host, description ); res.add( prev_mapping ); }catch( UPnPException e ){ if ( entries == 0 ){ break; } ok = false; throw(e); } } UPnPWANConnectionPortMapping[] res2= new UPnPWANConnectionPortMapping[res.size()]; res.toArray( res2 ); return( res2 ); } }finally{ for (int i=0;i<listeners.size();i++){ UPnPWANConnectionListener listener = (UPnPWANConnectionListener)listeners.get(i); try{ listener.mappingsReadResult( this, ok ); }catch( Throwable e){ Debug.printStackTrace(e); } } } } public String getExternalIPAddress() throws UPnPException { UPnPAction act = service.getAction( "GetExternalIPAddress" ); if ( act == null ){ log( "Action 'GetExternalIPAddress' not supported, binding not established" ); throw( new UPnPException( "GetExternalIPAddress not supported" )); }else{ UPnPActionInvocation inv = act.getInvocation(); UPnPActionArgument[] args = inv.invoke(); String ip = null; for (int i=0;i<args.length;i++){ UPnPActionArgument arg = args[i]; String name = arg.getName(); if ( name.equalsIgnoreCase("NewExternalIPAddress")){ ip = arg.getValue(); } } return( ip ); } } protected void log( String str ) { service.getDevice().getRootDevice().getUPnP().log( str ); } public void addListener( UPnPWANConnectionListener listener ) { listeners.add( listener ); } public void removeListener( UPnPWANConnectionListener listener ) { listeners.add( listener ); } private static class portMapping implements UPnPWANConnectionPortMapping { protected int external_port; protected boolean tcp; protected String internal_host; protected String description; protected portMapping( int _external_port, boolean _tcp, String _internal_host, String _description ) { external_port = _external_port; tcp = _tcp; internal_host = _internal_host; description = _description; } public boolean isTCP() { return( tcp ); } public int getExternalPort() { return( external_port ); } public String getInternalHost() { return( internal_host ); } public String getDescription() { return( description ); } protected String getString() { return( getDescription() + " [" + getExternalPort() + ":" + (isTCP()?"TCP":"UDP") + "]"); } } }