[jitsi-dev] [JsSIP] correct SIP ICE 183 behavior in JsSIP?


#1

(I've added dev@jitsi.org on CC because they produced ice4j and probably
know a lot more about this than me - Emil has also spoken about adding
SIP ICE support in Jitsi and it would be nice to have that interoperate
with JsSIP)

One more thing:

I don't want to mix signaling and media. This is, the INVITE can be
answered even if ICE/media connectivity checks fail. Note that INVITE
could be used to carry bodies other than SDP (for example MSRP or
other stuff not related to WebRTC or ICE).

What I propose is that, if the ICE connectivity fails then the call is
terminated, but I don't want to wait for the ICE connectivity to
success in order to run the SIP signaling.

What you propose leads to a situation where the callee is always alerted
(hears ringing) and forced to click answer before finding out if the
call can really work. That is called "Ghost ring" in the RFC. ICE is
meant to avoid that by checking connectivity before alerting the called
party.

It is recommended in RFC 5245:

http://tools.ietf.org/html/rfc5245#section-12.1.1

   "If an offer is received in an INVITE request, the answerer SHOULD
   begin to gather its candidates on receipt of the offer and then
   generate an answer in a provisional response once it has completed
   that process. ICE requires that a provisional response with an SDP
   be transmitted reliably. This can be done through the existing
   Provisional Response Acknowledgment (PRACK) mechanism [RFC3262] or
   through an optimization that is specific to ICE."

Notice it says "if an offer is received" - so there is no need to send
183 for every INVITE. Only send the 183 if
(a) the INVITE has SDP and
(b) the SDP contains ICE candidates.

It can also be done with PRACK if you don't like 183.

It also says "ICE requires that a provisional response with an SDP be
transmitted reliably"

A websocket transport is considered to be a reliable transport like TLS
or TCP, so sending a 183 over the websocket transport should satisfy
this requirement.

This is what I do in Lumicall:
https://github.com/opentelecoms-org/lumicall/blob/master/src/org/sipdroid/sipua/UserAgent.java#L1222

if(iceAgent != null) {
    handleRemoteSDPforICE(remote_sdp); // create local SDP
    call.respondProvisional(local_session); // send 183
    iceAgent.startConnectivityEstablishment(); // start ICE engine
}

The callee's phone does not start to ring until ICE checks confirm there
is a media path.

What I see is this:

1. caller sends INVITE with local candidates and no TURN candidates
2. JsSIP sends back 180
3. JsSIP alerts callee
4. callee clicks to answer
5. JsSIP sends 200 with candidates back to caller
6. browsers can only now start doing ICE checks, but caller/callee are
already told that call is answered and there is no media

Yes.

What should be happening:

1. caller sends INVITE with local candidates and no TURN candidates
2. JsSIP sends back 183 with candidates

At this point the callee is sending its IP address(es) information to
the caller in the 183's SDP, just because the caller sent him an
INVITE. Not good.

Please see my comments above about RFC 5245, s12.1.1. The RFC expects a
provisional response to be sent. What JsSIP is doing now is described
further down:

"Alternatively, an agent MAY delay sending an answer until the 200 OK;
however, this results in a poor user experience and is NOT
RECOMMENDED."

I realize this has privacy implications (sending local IPs back to the
caller before answering) but really that is how ICE works. The browser
could provide privacy by only using relay candidates. The SIP stack
could also strip local candidates from the SDP. But the SIP stack
should not defer sending the SDP.

Even so, there is still some information leak when the 180 is sent
containing things like a Contact header and User-Agent string.

3. browsers make connectivity checks

If the connectivity check succeeded then RTP begins (even before the
callee accepts the call). The callee (JsSIP) may avoid that by setting
a=inactive in all its media streams in the SDP sent in the 183, but
then in the 200 OK it should send the SAME SDP as in the 183 (SDP+SIP
rules) and hence a renegotiation (re-INVITE+200) would be required
after the user answers the call. Not good.

Actually, that is what is required by the RFC. The 200 should contain
the same SDP that was sent by the 183 (it says this in the same section
of the RFC, "Specifically, if the INVITE contained an offer, the same
answer appears in all of the 1xx and in the 2xx response to the INVITE.")

Then there should also be a re-INVITE to confirm the candidates that
were chosen by the ICE algorithm. In this case, Lumicall is not a good
example, I've been lazy and haven't implemented the re-INVITE yet.

Also notice:

"However, prior to this point, any media that needs to be sent towards
the caller (such as SIP early media [RFC3960]) MUST NOT be
transmitted. For this reason, implementations SHOULD delay alerting
the called party until candidates for each component of each media
stream have entered the valid list."

So sending the 183 doesn't imply you need to have audio ready to send.

4. if checks fail, caller should see error and callee should not hear
any ringing/incoming call notification

Problem of this scenario is described above.

Following the recommendations of the RFC
     "increases the post-dial delay, but has the
   effect of eliminating 'ghost rings'. Ghost rings are cases where the
   called party hears the phone ring, picks up, but hears nothing and
   cannot be heard."

5. JsSIP sends back 180

This would be useless as 183 has already been sent.

Well, it is not necessary.

The calling UA can start making a ringback sound to the user before ICE
has started. Or it could just display a message "Checking for
connectivity..." The calling UA will also know when ICE has completed
because the ICE engine will indicate success even before any further 1xx
signal comes over the wire. As soon as the ICE engine indicates
completion, the UA can start giving a ringback sound to the caller.

6. JsSIP alerts callee with ringing sounds
7. callee clicks to answer
8. JsSIP sends 200 with confirmed candidates back to caller

No, the SDP in the 200 MUST be equal than any previous one (unless the
complex 100rel extension, this is, PRACK, are being used, which is not
supported by JsSIP).

Sorry, that point 8 was a mistake. I agree that SDP must be equal in
1xx and 2xx responses, just as the RFC says. Then, if the selected
candidate was not in the c= line of the original SDP, there should be a
re-INVITE where the c= line contains the IP address of the selected
candidate. As I mentioned above, Lumicall doesn't do the re-INVITE but
it seems to work without it.

Does the latest JsSIP support sending the ICE candidates back in the 183?

No. That is early media and we don't want to implement early media
when JsSIP is the callee (early media just makes sense in the old PSTN
to hear "the person you are calling is unavailable, call again
later").

I'm not asking for full early-media support (e.g. simulated ringing
sounds), I'm just looking at how to get ICE working the way it is
recommended.

I also observed that when the browser has an ICE failure, JsSIP logs the
failure but still doesn't tell the application that the call is invalid.
E.g. I see this in the console:

[16:48:21.635] "Sat Aug 16 2014 16:48:21 GMT+0200 (CEST) |
jssip.rtcsession.rtcmediahandler | ICE connection state changed to "failed""

but the call still appears to be active.

Yes, there is room for improvement there, but it is not so easy. The
behavior of browsers is not stable enough and relying on the ICE
status is error prune for now.

Would you make this configurable perhaps? E.g. some option that can be
enabled to have JsSIP send BYE if the browser indicates ICE failure?
Then people who want the existing behavior can still have that.

Regards,

Daniel

···

On 16/08/14 17:44, Iñaki Baz Castillo wrote:

2014-08-16 17:41 GMT+02:00 Iñaki Baz Castillo <ibc@aliax.net>:

2014-08-16 17:12 GMT+02:00 Daniel Pocock <daniel@pocock.pro>:


#2

Hi Daniel,

Good points. Let me please re-check it after my holidays :wink:

···

2014-08-16 19:23 GMT+02:00 Daniel Pocock <daniel@pocock.pro>:

(I've added dev@jitsi.org on CC because they produced ice4j and probably
know a lot more about this than me - Emil has also spoken about adding
SIP ICE support in Jitsi and it would be nice to have that interoperate
with JsSIP)

On 16/08/14 17:44, Iñaki Baz Castillo wrote:

One more thing:

I don't want to mix signaling and media. This is, the INVITE can be
answered even if ICE/media connectivity checks fail. Note that INVITE
could be used to carry bodies other than SDP (for example MSRP or
other stuff not related to WebRTC or ICE).

What I propose is that, if the ICE connectivity fails then the call is
terminated, but I don't want to wait for the ICE connectivity to
success in order to run the SIP signaling.

What you propose leads to a situation where the callee is always alerted
(hears ringing) and forced to click answer before finding out if the
call can really work. That is called "Ghost ring" in the RFC. ICE is
meant to avoid that by checking connectivity before alerting the called
party.

It is recommended in RFC 5245:

http://tools.ietf.org/html/rfc5245#section-12.1.1

   "If an offer is received in an INVITE request, the answerer SHOULD
   begin to gather its candidates on receipt of the offer and then
   generate an answer in a provisional response once it has completed
   that process. ICE requires that a provisional response with an SDP
   be transmitted reliably. This can be done through the existing
   Provisional Response Acknowledgment (PRACK) mechanism [RFC3262] or
   through an optimization that is specific to ICE."

Notice it says "if an offer is received" - so there is no need to send
183 for every INVITE. Only send the 183 if
(a) the INVITE has SDP and
(b) the SDP contains ICE candidates.

It can also be done with PRACK if you don't like 183.

It also says "ICE requires that a provisional response with an SDP be
transmitted reliably"

A websocket transport is considered to be a reliable transport like TLS
or TCP, so sending a 183 over the websocket transport should satisfy
this requirement.

This is what I do in Lumicall:
https://github.com/opentelecoms-org/lumicall/blob/master/src/org/sipdroid/sipua/UserAgent.java#L1222

if(iceAgent != null) {
    handleRemoteSDPforICE(remote_sdp); // create local SDP
    call.respondProvisional(local_session); // send 183
    iceAgent.startConnectivityEstablishment(); // start ICE engine
}

The callee's phone does not start to ring until ICE checks confirm there
is a media path.

2014-08-16 17:41 GMT+02:00 Iñaki Baz Castillo <ibc@aliax.net>:

2014-08-16 17:12 GMT+02:00 Daniel Pocock <daniel@pocock.pro>:

What I see is this:

1. caller sends INVITE with local candidates and no TURN candidates
2. JsSIP sends back 180
3. JsSIP alerts callee
4. callee clicks to answer
5. JsSIP sends 200 with candidates back to caller
6. browsers can only now start doing ICE checks, but caller/callee are
already told that call is answered and there is no media

Yes.

What should be happening:

1. caller sends INVITE with local candidates and no TURN candidates
2. JsSIP sends back 183 with candidates

At this point the callee is sending its IP address(es) information to
the caller in the 183's SDP, just because the caller sent him an
INVITE. Not good.

Please see my comments above about RFC 5245, s12.1.1. The RFC expects a
provisional response to be sent. What JsSIP is doing now is described
further down:

"Alternatively, an agent MAY delay sending an answer until the 200 OK;
however, this results in a poor user experience and is NOT
RECOMMENDED."

I realize this has privacy implications (sending local IPs back to the
caller before answering) but really that is how ICE works. The browser
could provide privacy by only using relay candidates. The SIP stack
could also strip local candidates from the SDP. But the SIP stack
should not defer sending the SDP.

Even so, there is still some information leak when the 180 is sent
containing things like a Contact header and User-Agent string.

3. browsers make connectivity checks

If the connectivity check succeeded then RTP begins (even before the
callee accepts the call). The callee (JsSIP) may avoid that by setting
a=inactive in all its media streams in the SDP sent in the 183, but
then in the 200 OK it should send the SAME SDP as in the 183 (SDP+SIP
rules) and hence a renegotiation (re-INVITE+200) would be required
after the user answers the call. Not good.

Actually, that is what is required by the RFC. The 200 should contain
the same SDP that was sent by the 183 (it says this in the same section
of the RFC, "Specifically, if the INVITE contained an offer, the same
answer appears in all of the 1xx and in the 2xx response to the INVITE.")

Then there should also be a re-INVITE to confirm the candidates that
were chosen by the ICE algorithm. In this case, Lumicall is not a good
example, I've been lazy and haven't implemented the re-INVITE yet.

Also notice:

"However, prior to this point, any media that needs to be sent towards
the caller (such as SIP early media [RFC3960]) MUST NOT be
transmitted. For this reason, implementations SHOULD delay alerting
the called party until candidates for each component of each media
stream have entered the valid list."

So sending the 183 doesn't imply you need to have audio ready to send.

4. if checks fail, caller should see error and callee should not hear
any ringing/incoming call notification

Problem of this scenario is described above.

Following the recommendations of the RFC
     "increases the post-dial delay, but has the
   effect of eliminating 'ghost rings'. Ghost rings are cases where the
   called party hears the phone ring, picks up, but hears nothing and
   cannot be heard."

5. JsSIP sends back 180

This would be useless as 183 has already been sent.

Well, it is not necessary.

The calling UA can start making a ringback sound to the user before ICE
has started. Or it could just display a message "Checking for
connectivity..." The calling UA will also know when ICE has completed
because the ICE engine will indicate success even before any further 1xx
signal comes over the wire. As soon as the ICE engine indicates
completion, the UA can start giving a ringback sound to the caller.

6. JsSIP alerts callee with ringing sounds
7. callee clicks to answer
8. JsSIP sends 200 with confirmed candidates back to caller

No, the SDP in the 200 MUST be equal than any previous one (unless the
complex 100rel extension, this is, PRACK, are being used, which is not
supported by JsSIP).

Sorry, that point 8 was a mistake. I agree that SDP must be equal in
1xx and 2xx responses, just as the RFC says. Then, if the selected
candidate was not in the c= line of the original SDP, there should be a
re-INVITE where the c= line contains the IP address of the selected
candidate. As I mentioned above, Lumicall doesn't do the re-INVITE but
it seems to work without it.

Does the latest JsSIP support sending the ICE candidates back in the 183?

No. That is early media and we don't want to implement early media
when JsSIP is the callee (early media just makes sense in the old PSTN
to hear "the person you are calling is unavailable, call again
later").

I'm not asking for full early-media support (e.g. simulated ringing
sounds), I'm just looking at how to get ICE working the way it is
recommended.

I also observed that when the browser has an ICE failure, JsSIP logs the
failure but still doesn't tell the application that the call is invalid.
E.g. I see this in the console:

[16:48:21.635] "Sat Aug 16 2014 16:48:21 GMT+0200 (CEST) |
jssip.rtcsession.rtcmediahandler | ICE connection state changed to "failed""

but the call still appears to be active.

Yes, there is room for improvement there, but it is not so easy. The
behavior of browsers is not stable enough and relying on the ICE
status is error prune for now.

Would you make this configurable perhaps? E.g. some option that can be
enabled to have JsSIP send BYE if the browser indicates ICE failure?
Then people who want the existing behavior can still have that.

Regards,

Daniel

--
You received this message because you are subscribed to the Google Groups "JsSIP" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jssip+unsubscribe@googlegroups.com.
To post to this group, send email to jssip@googlegroups.com.
Visit this group at http://groups.google.com/group/jssip.
For more options, visit https://groups.google.com/d/optout.

--
Iñaki Baz Castillo
<ibc@aliax.net>