From bb4b2ac009d2749a36fd69f6b1dcaf4befe064e4 Mon Sep 17 00:00:00 2001 From: Emil Ivov Date: Mon, 24 Feb 2014 23:51:13 +0200 Subject: [PATCH] Ongoing work on integrating ICE for SIP --- .../jabber/IceUdpTransportManager.java | 248 +++++------------- .../sip/CallPeerMediaHandlerSipImpl.java | 4 +- .../sip/IceTransportManagerSipImpl.java | 1 - .../service/protocol/AccountID.java | 15 +- .../protocol/media/TransportManager.java | 135 +++++++++- 5 files changed, 201 insertions(+), 202 deletions(-) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java index d3280cbf5..318241931 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java @@ -65,16 +65,6 @@ public class IceUdpTransportManager */ protected final Agent iceAgent; - /** - * Default STUN server address. - */ - protected static final String DEFAULT_STUN_SERVER_ADDRESS = "stun.jitsi.net"; - - /** - * Default STUN server port. - */ - protected static final int DEFAULT_STUN_SERVER_PORT = 3478; - /** * Creates a new instance of this transport manager, binding it to the * specified peer. @@ -86,187 +76,85 @@ public IceUdpTransportManager(CallPeerJabberImpl callPeer) { super(callPeer); iceAgent = createIceAgent(); + + //FIXME: initiator == controlling is only true the first time in a call + //and not for ice restarts. ICE is smart so this isn't breaking anything + //but it still sucks. + iceAgent.setControlling(!callPeer.isInitiator()); + iceAgent.addStateChangeListener(this); } + /** - * Creates the ICE agent that we would be using in this transport manager - * for all negotiation. + * Discovers and returns a list of dynamically obtained (as opposed to + * statically configured) STUN/TURN servers for use with this account. This + * specific implementation would only implement DNS-based discovery on the + * domain of the service that is currently in use. + *

+ * It is meant to later provide additional discovery methods. + * XEP-0215 for example. * - * @return the ICE agent to use for all the ICE negotiation that this - * transport manager would be going through + * @return */ - protected Agent createIceAgent() + protected StunCandidateHarvester discoverStunServerForAccount() { - long startGatheringHarvesterTime = System.currentTimeMillis(); CallPeerJabberImpl peer = getCallPeer(); ProtocolProviderServiceJabberImpl provider = peer.getProtocolProvider(); NetworkAddressManagerService namSer = getNetAddrMgr(); - boolean atLeastOneStunServer = false; - Agent agent = namSer.createIceAgent(); - - /* - * XEP-0176: the initiator MUST include the ICE-CONTROLLING attribute, - * the responder MUST include the ICE-CONTROLLED attribute. - */ - agent.setControlling(!peer.isInitiator()); - - //we will now create the harvesters JabberAccountIDImpl accID = (JabberAccountIDImpl) provider.getAccountID(); - if (accID.isStunServerDiscoveryEnabled()) + //the default server is supposed to use the same user name and + //password as the account itself. + String username + = org.jivesoftware.smack.util.StringUtils.parseName( + provider.getOurJID()); + String password + = JabberActivator.getProtocolProviderFactory().loadPassword( + accID); + UserCredentials credentials = provider.getUserCredentials(); + + if(credentials != null) + password = credentials.getPasswordAsString(); + + // ask for password if not saved + if (password == null) { - //the default server is supposed to use the same user name and - //password as the account itself. - String username - = org.jivesoftware.smack.util.StringUtils.parseName( - provider.getOurJID()); - String password - = JabberActivator.getProtocolProviderFactory().loadPassword( - accID); - UserCredentials credentials = provider.getUserCredentials(); - - if(credentials != null) - password = credentials.getPasswordAsString(); - - // ask for password if not saved - if (password == null) + //create a default credentials object + credentials = new UserCredentials(); + credentials.setUserName(accID.getUserID()); + //request a password from the user + credentials + = provider.getAuthority().obtainCredentials( + accID.getDisplayName(), + credentials, + SecurityAuthority.AUTHENTICATION_REQUIRED); + + // in case user has canceled the login window + if(credentials == null) + return null; + + //extract the password the user passed us. + char[] pass = credentials.getPassword(); + + // the user didn't provide us a password (i.e. canceled the + // operation) + if(pass == null) + return null; + password = new String(pass); + + if (credentials.isPasswordPersistent()) { - //create a default credentials object - credentials = new UserCredentials(); - credentials.setUserName(accID.getUserID()); - //request a password from the user - credentials - = provider.getAuthority().obtainCredentials( - accID.getDisplayName(), - credentials, - SecurityAuthority.AUTHENTICATION_REQUIRED); - - // in case user has canceled the login window - if(credentials == null) - return null; - - //extract the password the user passed us. - char[] pass = credentials.getPassword(); - - // the user didn't provide us a password (i.e. canceled the - // operation) - if(pass == null) - return null; - password = new String(pass); - - if (credentials.isPasswordPersistent()) - { - JabberActivator.getProtocolProviderFactory() - .storePassword(accID, password); - } - } - - StunCandidateHarvester autoHarvester - = namSer.discoverStunServer( - accID.getService(), - StringUtils.getUTF8Bytes(username), - StringUtils.getUTF8Bytes(password)); - - if (logger.isInfoEnabled()) - logger.info("Auto discovered harvester is " + autoHarvester); - - if (autoHarvester != null) - { - atLeastOneStunServer = true; - agent.addCandidateHarvester(autoHarvester); + JabberActivator.getProtocolProviderFactory() + .storePassword(accID, password); } } - //now create stun server descriptors for whatever other STUN/TURN - //servers the user may have set. - for(StunServerDescriptor desc : accID.getStunServers()) - { - TransportAddress addr - = new TransportAddress( - desc.getAddress(), - desc.getPort(), - Transport.UDP); - - // if we get STUN server from automatic discovery, it may just - // be server name (i.e. stun.domain.org) and it may be possible that - // it cannot be resolved - if(addr.getAddress() == null) - { - logger.info("Unresolved address for " + addr); - continue; - } - - StunCandidateHarvester harvester; - - if(desc.isTurnSupported()) - { - //Yay! a TURN server - harvester - = new TurnCandidateHarvester( - addr, - new LongTermCredential( - desc.getUsername(), - desc.getPassword())); - } - else - { - //this is a STUN only server - harvester = new StunCandidateHarvester(addr); - } - - if (logger.isInfoEnabled()) - logger.info("Adding pre-configured harvester " + harvester); - - atLeastOneStunServer = true; - agent.addCandidateHarvester(harvester); - } - - if(!atLeastOneStunServer && accID.isUseDefaultStunServer()) - { - /* we have no configured or discovered STUN server so takes the - * default provided by us if user allows it - */ - TransportAddress addr - = new TransportAddress( - DEFAULT_STUN_SERVER_ADDRESS, - DEFAULT_STUN_SERVER_PORT, - Transport.UDP); - - agent.addCandidateHarvester(new StunCandidateHarvester(addr)); - } - - /* Jingle nodes candidate */ - if(accID.isJingleNodesRelayEnabled()) - { - /* this method is blocking until Jingle Nodes auto-discovery (if - * enabled) finished - */ - SmackServiceNode serviceNode = provider.getJingleNodesServiceNode(); - - if(serviceNode != null) - { - agent.addCandidateHarvester( - new JingleNodesHarvester(serviceNode)); - } - } - - if(accID.isUPNPEnabled()) - agent.addCandidateHarvester(new UPNPHarvester()); - - long stopGatheringHarvesterTime = System.currentTimeMillis(); - - if (logger.isInfoEnabled()) - { - long gatheringHarvesterTime - = stopGatheringHarvesterTime - startGatheringHarvesterTime; - - logger.info( - "End gathering harvester within " + gatheringHarvesterTime - + " ms"); - } - return agent; + return namSer.discoverStunServer( + accID.getService(), + StringUtils.getUTF8Bytes(username), + StringUtils.getUTF8Bytes(password)); } /** @@ -603,7 +491,6 @@ public void startCandidateHarvest( throws OperationFailedException { this.cpeList = ourAnswer; - super.startCandidateHarvest(theirOffer, ourAnswer, transportInfoSender); } @@ -769,19 +656,6 @@ public List wrapupCandidateHarvest() return cpeList; } - /** - * Returns a reference to the {@link NetworkAddressManagerService}. The only - * reason this method exists is that {@link JabberActivator - * #getNetworkAddressManagerService()} is too long to write and makes code - * look clumsy. - * - * @return a reference to the {@link NetworkAddressManagerService}. - */ - private static NetworkAddressManagerService getNetAddrMgr() - { - return JabberActivator.getNetworkAddressManagerService(); - } - /** * Starts the connectivity establishment of the associated ICE * Agent. diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java index 59fe3e0ce..ea18c7b14 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java @@ -157,8 +157,8 @@ private SessionDescription createFirstOffer() mediaDescs); //ICE HACK - please fix - //new IceTransportManagerSipImpl(getPeer()).startCandidateHarvest( - // sDes, null, false, false, false, false, false ); + new IceTransportManagerSipImpl(getPeer()).startCandidateHarvest( + sDes, null, false, false, false, false, false ); this.localSess = sDes; return localSess; diff --git a/src/net/java/sip/communicator/impl/protocol/sip/IceTransportManagerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/IceTransportManagerSipImpl.java index e300a6edb..1bebe3265 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/IceTransportManagerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/IceTransportManagerSipImpl.java @@ -84,7 +84,6 @@ public void startCandidateHarvest(SessionDescription ourOffer, throws OperationFailedException { iceAgent = createIceAgent(); - iceAgent.setControlling(true); //obviously we ARE the controlling agent since we are the ones creating //the offer. diff --git a/src/net/java/sip/communicator/service/protocol/AccountID.java b/src/net/java/sip/communicator/service/protocol/AccountID.java index af47b9911..b2366aefb 100644 --- a/src/net/java/sip/communicator/service/protocol/AccountID.java +++ b/src/net/java/sip/communicator/service/protocol/AccountID.java @@ -802,8 +802,7 @@ public boolean isEncryptionProtocolEnabled(String encryptionProtocolName) * @return the list of STUN servers that this account is currently * configured to use. */ - public List getStunServers( - BundleContext bundleContext) + public List getStunServers() { Map accountProperties = getAccountProperties(); List stunServerList @@ -823,7 +822,6 @@ public List getStunServers( break; String password = this.loadStunPassword( - bundleContext, this, ProtocolProviderFactory.STUN_PREFIX + i); @@ -839,20 +837,17 @@ public List getStunServers( /** * Returns the password for the STUN server with the specified prefix. * - * @param bundleContext the OSGi bundle context that we are currently - * running in. * @param accountID account ID * @param namePrefix name prefix * * @return password or null if empty */ - protected static String loadStunPassword(BundleContext bundleContext, - AccountID accountID, + protected static String loadStunPassword(AccountID accountID, String namePrefix) { ProtocolProviderFactory providerFactory = ProtocolProviderFactory.getProtocolProviderFactory( - bundleContext, + ProtocolProviderActivator.getBundleContext(), accountID.getSystemProtocolName()); String password = null; @@ -861,12 +856,12 @@ protected static String loadStunPassword(BundleContext bundleContext, = className.substring(0, className.lastIndexOf('.')); String accountPrefix = ProtocolProviderFactory.findAccountPrefix( - bundleContext, + ProtocolProviderActivator.getBundleContext(), accountID, packageSourceName); CredentialsStorageService credentialsService = ServiceUtils.getService( - bundleContext, + ProtocolProviderActivator.getBundleContext(), CredentialsStorageService.class); try diff --git a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java index 3be2a98d8..053aadf41 100644 --- a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java +++ b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java @@ -12,9 +12,14 @@ import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.Logger; +import org.ice4j.*; import org.ice4j.ice.*; +import org.ice4j.ice.harvest.*; +import org.ice4j.security.*; import org.jitsi.service.configuration.*; import org.jitsi.service.neomedia.*; +import org.jitsi.util.*; /** * TransportManagers are responsible for allocating ports, gathering @@ -36,6 +41,16 @@ public abstract class TransportManager> private static final Logger logger = Logger.getLogger(TransportManager.class); + /** + * Default STUN server address. + */ + protected static final String DEFAULT_STUN_SERVER_ADDRESS = "stun.jitsi.net"; + + /** + * Default STUN server port. + */ + protected static final int DEFAULT_STUN_SERVER_PORT = 3478; + /** * The port tracker that we should use when binding generic media streams. *

@@ -783,6 +798,22 @@ public static String getICECandidateExtendedType( return null; } + /** + * Discovers and returns a list of dynamically obtained (as opposed to + * statically configured) STUN/TURN servers for use with this account. This + * specific implementation would only implement DNS-based discovery on the + * domain of the service that is currently in use. Protocol-specific + * implementations can provide additional discovery methods, such as + * XEP-0215 for example. + * + * @return + */ + protected StunCandidateHarvester discoverStunServerForAccount() + { + //TODO: add a default implementation here. + return null; + } + /** * Creates the ICE agent that we would be using in this transport manager @@ -793,10 +824,98 @@ public static String getICECandidateExtendedType( */ protected Agent createIceAgent() { - //work in progress - return null; + ProtocolProviderService provider = getCallPeer().getProtocolProvider(); + NetworkAddressManagerService namSer = getNetAddrMgr(); + boolean atLeastOneStunServer = false; + Agent agent = namSer.createIceAgent(); + + //we will now create the harvesters + AccountID accID = provider.getAccountID(); + + if (accID.isStunServerDiscoveryEnabled()) + { + StunCandidateHarvester autoHarvester + = discoverStunServerForAccount(); + + if(autoHarvester != null) + { + + if (logger.isInfoEnabled()) + logger.info("Auto discovered harvester: " + autoHarvester); + + if (autoHarvester != null) + { + atLeastOneStunServer = true; + agent.addCandidateHarvester(autoHarvester); + } + } + } + + //now create stun server descriptors for whatever other STUN/TURN + //servers the user may have set. + for(StunServerDescriptor desc : accID.getStunServers()) + { + TransportAddress addr + = new TransportAddress( + desc.getAddress(), + desc.getPort(), + Transport.UDP); + + // if we get STUN server from automatic discovery, it may just + // be server name (i.e. stun.domain.org) and it may be possible that + // it cannot be resolved + if(addr.getAddress() == null) + { + logger.info("Unresolved address for " + addr); + continue; + } + + StunCandidateHarvester harvester; + + if(desc.isTurnSupported()) + { + //Yay! a TURN server + harvester + = new TurnCandidateHarvester( + addr, + new LongTermCredential( + desc.getUsername(), + desc.getPassword())); + } + else + { + //this is a STUN only server + harvester = new StunCandidateHarvester(addr); + } + + if (logger.isInfoEnabled()) + logger.info("Adding pre-configured harvester " + harvester); + + atLeastOneStunServer = true; + agent.addCandidateHarvester(harvester); + } + + if(!atLeastOneStunServer && accID.isUseDefaultStunServer()) + { + /* we have no configured or discovered STUN server so takes the + * default provided by us if user allows it + */ + TransportAddress addr + = new TransportAddress( + DEFAULT_STUN_SERVER_ADDRESS, + DEFAULT_STUN_SERVER_PORT, + Transport.UDP); + + agent.addCandidateHarvester(new StunCandidateHarvester(addr)); + } + + if(accID.isUPNPEnabled()) + agent.addCandidateHarvester(new UPNPHarvester()); + + return agent; } + /** * Creates an {@link IceMediaStream} with the specified media * name. @@ -815,4 +934,16 @@ protected IceMediaStream createIceStream(String media, Agent agent) { return null; } + + /** + * Returns a reference to the {@link NetworkAddressManagerService}. This is + * only a convenience method. + * + * @return a reference to the {@link NetworkAddressManagerService} + * currently in use in our environment. + */ + public static NetworkAddressManagerService getNetAddrMgr() + { + return ProtocolMediaActivator.getNetworkAddressManagerService(); + } }