[jitsi-dev] [ice4j] Unable to maintain STUN Binding / multiple instance (50+) of ServTran thread


#1

Hello,

I'm currently trying to receive Chrome WebRTC streams with a java server.
Ice4j is used to generate the candidates and to manage the STUN
connectivity check.

This work fine (I'm able to receive the stream and sent it back) for the
first seconds (less than 10 seconds).

After this delay, my stream is cut off, and a wireshark capture show a lot
of unanswered binding request.

By digging in my logs, I found a call to TerminationThread in agent (which
is stopping all connectivity check, for server and client).

The log trace :

*févr. 05, 2013 5:08:08 PM org.ice4j.ice.Agent incomingCheckReceived*
*INFO: Receive check from /192.168.2.119:15000/udp -> /
192.168.2.119:51566/udp (audio.1)triggered a check*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.ConnectivityCheckClient
processSuccessResponse*
*INFO: Pair succeeded: /192.168.2.119:15000/udp ->
/192.168.2.119:51566/udp(audio.1)

···

*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.ConnectivityCheckClient
processSuccessResponse*
*INFO: Pair validated: /192.168.2.119:15000/udp ->
/192.168.2.119:51566/udp(audio.1)
*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.ConnectivityCheckClient
processSuccessResponse*
*INFO: IsControlling: false USE-CANDIDATE:false*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.ConnectivityCheckClient
processSuccessResponse*
*INFO: Nomination confirmed for pair: /192.168.2.119:15000/udp -> /
192.168.2.119:51566/udp (audio.1)*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.CheckList handleNominationConfirmed
*
*INFO: Selected pair for stream audio.1: /192.168.2.119:15000/udp -> /
192.168.2.119:51566/udp (audio.1)*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.Agent incomingCheckReceived*
*INFO: Receive check from /192.168.2.119:15000/udp -> /
192.168.2.119:51566/udp (audio.1)triggered a check*
*févr. 05, 2013 5:08:08 PM org.ice4j.socket.DelegatingDatagramSocket
logRtpLosses*
*INFO: RTP lost > 5%: 0.3333333333333333*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.Agent checkListStatesUpdated*
*INFO: CheckList of stream audio is COMPLETED*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.Agent checkListStatesUpdated*
*INFO: ICE state is COMPLETED*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.Agent checkListStatesUpdated*
*INFO: Harvester selected for audio.1 host*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.Agent checkListStatesUpdated*
*INFO: CheckList of stream audio is COMPLETED*
*févr. 05, 2013 5:08:08 PM org.ice4j.ice.Agent checkListStatesUpdated*
*INFO: CheckList of stream audio is COMPLETED*
*févr. 05, 2013 5:08:11 PM org.ice4j.ice.Agent$TerminationThread run*
*INFO: ICE state is TERMINATED*

Using jconsole, I found that my server has the following threads active
during this period :
- StunKeepAliveThread
- Stun4J Message Processor (3)
- IceConnector@<id> (4)
- ServTran (a lot, my process is peaking at 370 threads).

It seems that my agent can find existing STUN transaction in order to
properly manage retransmission.
How must I initialize my agent in order to fix this ?

This is how I do it at the moment (I start from Ice.java in ice4j test
examples) :

*public class IceManager {*
*
*
* private static Logger __logger =
LoggerFactory.getLogger(IceManager.class);*
*
*
* private boolean _rtcpMux;*
* private Agent _agent;*
* private boolean _bundle;*
* private SdpFactory _sdpFactory;*
* private SdpSession _sdpSession;*

*public IceManager(SdpSession session, int minRtpPort, boolean rtcpMux,
boolean bundle) {*
* _rtcpMux = rtcpMux;*
* _bundle = bundle;*
* _sdpSession = session;*
* _sdpFactory = session.getSdpManager().getSdpFactory();*
* try {*
* _agent = createAgent(minRtpPort);*
* _agent.setControlling(false);*
* _agent.setNominationStrategy(NominationStrategy.NOMINATE_HIGHEST_PRIO);*
* manageConnectivityCheck();*
* } catch (Throwable e) {*
* __logger.error("Unable to create agent : {}", e.getMessage());*
* } *
* }*
*
*
* private void manageConnectivityCheck() {*
* // Add remote ICE peers to local agent from SDP*
* List<IceMediaStream> streams = _agent.getStreams();*
* for (IceMediaStream stream : streams) {*
* SdpMedia media = _sdpSession.getOfferMedia(stream.getName()); // Get
media from stream name *
* *
* // Set remote ICE parameters*
* stream.setRemotePassword(media.getIcePassword());*
* stream.setRemoteUfrag(media.getIceUfrag());*
* // Add remote candidates to stream*
* List<Attribute> candidates = media.getIceCandidates();*
* for (Attribute candidate : candidates) {*
* parseCandidate(candidate, stream); *
* }*
* // Set default candidates*
* Component rtpComponent = stream.getComponent(Component.RTP);*
* Component rtcpComponent = stream.getComponent(Component.RTCP);*
*
*
* TransportAddress defaultRtpAddress = new
TransportAddress(media.getAddress(), media.getPort(), Transport.UDP);*
* Candidate<RemoteCandidate> defaultRtpCandidate =
rtpComponent.findRemoteCandidate(defaultRtpAddress);*
* rtpComponent.setDefaultRemoteCandidate(defaultRtpCandidate);*
*
*
* if(rtcpComponent != null)*
* {*
* TransportAddress defaultRtcpAddress = new
TransportAddress(media.getAddress(), media.getRtcpPort(), Transport.UDP);*
* Candidate<RemoteCandidate> defaultRtcpCandidate =
rtcpComponent.findRemoteCandidate(defaultRtcpAddress);*
*
rtcpComponent.setDefaultRemoteCandidate(defaultRtcpCandidate);*
* }*
* *
* }*
* // Start STUN connectivity check in order to selection current peers*
* _agent.startConnectivityEstablishment();*
* }*
*
*
*
private RemoteCandidate parseCandidate(Attribute candidate, IceMediaStream
stream) {
String value = null;

        try{
            value = candidate.getValue();
        } catch (Throwable t){
        //can't happen
        }

        StringTokenizer tokenizer = new StringTokenizer(value);

        String foundation = tokenizer.nextToken();
        int componentID = Integer.parseInt( tokenizer.nextToken() );
        Transport transport = Transport.parse(tokenizer.nextToken());
        long priority = Long.parseLong(tokenizer.nextToken());
        String address = tokenizer.nextToken();
        int port = Integer.parseInt(tokenizer.nextToken());

        TransportAddress transAddr = new TransportAddress(address, port,
transport);

        tokenizer.nextToken(); //skip the "typ" String
        CandidateType type = CandidateType.parse(tokenizer.nextToken());

        Component component = stream.getComponent(componentID);

        if(component == null)
            return null;

        // check if there's a related address property
        RemoteCandidate relatedCandidate = null;
        if (tokenizer.countTokens() >= 4) {
            tokenizer.nextToken(); // skip the raddr element
            String relatedAddr = tokenizer.nextToken();
            tokenizer.nextToken(); // skip the rport element
            int relatedPort = Integer.parseInt(tokenizer.nextToken());

            TransportAddress raddr = new TransportAddress(relatedAddr,
relatedPort, Transport.UDP);
            relatedCandidate = component.findRemoteCandidate(raddr);
        }

        RemoteCandidate cand = new RemoteCandidate(transAddr, component,
type, foundation, priority, relatedCandidate);
        component.addRemoteCandidate(cand);

        return cand;
}

}
*

Note : I'm using JAIN-SDP for SDP parsing, and I have created two helpers
class :
- SdpSession, which handle SDP parsing, answer generation and store the SDP
offer.
- SdpMedia, which allow me a quick access to ICE related attributes in each
media description.

Regards,
--
Pierrick Grasland