WEBRTC drops connection due to ICE restart

1,001 views
Skip to first unread message

Robert Dyck

unread,
Dec 10, 2021, 5:17:00 PM12/10/21
to rtpengine
Although there is a SIP context to my problem, I believe the central issue involves the interaction between WEBRTC and rtpengine. 

ICE support is mandatory in WEBRTC. The sad reality is that there many old phones and adapters, hardware or software, that don't support ICE. Rtpengine supports ICE and it can be told use ICE when connecting to the WEBRTC side of the connection. So far so good.

There are various reason an entity on either end of the connection might want to send a new offer while maintaining the existing connection. When this happens the entities keep the same ICE tags ( ufrag and pwd ) that they used to set up the connection in the first place regardless their current role ( offer or answer ).

Of the rtpengine ICE flags there are a couple that tell rtpengine to act as an ICE agent. Unfortuantely each time they are invoked they present new ICE tags. In ICE land that is the definition of an ICE restart. Chrome and Firefox drop the connection when they receive the restart request. Firefox gives a very clear error message, chrome not so much.
" Remote description indicates ICE restart but offer did not request ICE restart"

Is there any way to cofigure around this problem?

Richard Fuchs

unread,
Dec 10, 2021, 6:35:05 PM12/10/21
to rtpe...@googlegroups.com
On 10/12/2021 17.17, [EXT] Robert Dyck wrote:
There are various reason an entity on either end of the connection might want to send a new offer while maintaining the existing connection. When this happens the entities keep the same ICE tags ( ufrag and pwd ) that they used to set up the connection in the first place regardless their current role ( offer or answer ).

Of the rtpengine ICE flags there are a couple that tell rtpengine to act as an ICE agent. Unfortuantely each time they are invoked they present new ICE tags. In ICE land that is the definition of an ICE restart. Chrome and Firefox drop the connection when they receive the restart request. Firefox gives a very clear error message, chrome not so much.
" Remote description indicates ICE restart but offer did not request ICE restart"

Is there any way to cofigure around this problem?

Emphasis mine. No it doesn't do that. It generates new attributes only when the call/dialogue is deleted between the invocations, or if the from/to-tags are different (because that would tell rtpengine that it's talking to a different endpoint), or perhaps if there's some other booboo in the signalling to rtpengine that I can't think of right now. So, post a log that shows your signalling.

Cheers

Richard Fuchs

unread,
Dec 12, 2021, 8:30:57 AM12/12/21
to [EXT] Robert Dyck, rtpe...@googlegroups.com
This only shows that the ICE attributes change, which you already mentioned, so this isn't really helpful. What would be a lot more helpful is what I asked for originally: logs from rtpengine that show the signalling to rtpengine. You may have your offers/answers swapped or the SIP tags swapped or something like that.

Cheers


On 11/12/2021 17.59, [EXT] Robert Dyck wrote:
First off we can eliminate Chrome. What I saw previously appears to be unrelated to the ICE  thing. But it gets strange. If Twinkle on host A calls WEBRTC on host B and host B then initiates call Hold, host B drops the call. Repeating with host A calling host B , now host B initiates call Hold/Resume, it's all good.

If host B WEBRTC calls host A Twinkle Hold/Resume works fine every which way. If Twinkle is run on host B and WEBRTC is on host A, I can't make Hold/Resume fail. The same web app is used on all the calls. The trouble only occurs with WEBRTC running on host B ( HP laptop ).

In the failure scenario I don't see any dialog change just the ICE tags change. The hosts are side by side on the local wired netwrk.

The dialog

invite

To: <sip:4...@192.168.1.2>
From: <sip:7...@192.168.1.2>;tag=dxosw
Call-ID: qkzhitbmvrhsung@blacky

a=ice-ufrag:88qu07Uu
a=ice-pwd:pbnrSsr1b1gVjYO6g86SavMqOh

answer

To: <sip:4...@192.168.1.2>;tag=cgdbb4qsa2
From: <sip:7...@192.168.1.2>;tag=dxosw
Call-ID: qkzhitbmvrhsung@blacky

a=ice-pwd:760f49d6c39f1d2205ee014160c5fdc3
a=ice-ufrag:16fbb7a9

webrtc goes on Hold
 
To: <sip:7...@192.168.1.2>;tag=dxosw
From: <sip:4...@192.168.1.2>;tag=cgdbb4qsa2
Call-ID: qkzhitbmvrhsung@blacky

a=ice-ufrag:16fbb7a9
a=ice-pwd:760f49d6c39f1d2205ee014160c5fdc3

answer

To: <sip:7...@192.168.1.2>;tag=dxosw
From: <sip:4...@192.168.1.2>;tag=cgdbb4qsa2
Call-ID: qkzhitbmvrhsung@blacky

a=ice-ufrag:dDiWsVrl
a=ice-pwd:gxFhDjfB4iIfBxi5bR5dIpeVmq

webrtc sends bye

Richard Fuchs

unread,
Dec 13, 2021, 7:55:32 AM12/13/21
to [EXT] Robert Dyck, rtpe...@googlegroups.com
On 12/12/2021 11.44, [EXT] Robert Dyck wrote:
> Hmm I tried attaching a file but I don't see it here, Trying again.

Probably because you keep sending these emails to me privately instead
of to the mailing list 😛

The problem is your usage of `via-branch=0` in all signalling. The
via-branch sort-of overrides the to-tag, so using the same via-branch
string in all signalling makes rtpengine think that the second set of
offer/answer is going in the same direction as the first one when it
really is reversed. This is also why you see the same port coming out of
the second answer as the second offer.

So either supply the actual branch string from the via header (or let
the Kamailio module handle it if that's what you're using - it supports
values `1`, `2`, or `auto`), or make up your own strings and supply
those (they just need to be different in the two offer/answer exchanges
because they're travelling in opposite directions), or omit the
`via-branch` option altogether if you're not doing any branching.

As an unrelated suggestion, you probably don't want to use
`ICE=optional` as with that option media may end up bypassing rtpengine,
which is usually not what you want. Use `ICE=force` if you know the
message is going to a WebRTC endpoint. You can also use `ICE=force`
unconditionally everywhere if you know that non-ICE endpoints don't have
a problem with ICE attributes present. If you're distinguishing between
initial offers and re-offers (re-invites) then you can usually omit
`ICE=...` in all signalling except the initial offer.

HTH

Cheers

Robert Dyck

unread,
Dec 13, 2021, 2:09:02 PM12/13/21
to rtpengine
When I migrated to rtpengine from rtpproxy I had trouble with forked calls. By adding the branch ID ( not the via branch parameter but an integer 0 to whatever ) the  problem with forked calls was resolved. Of course for in-dialog requests it will always be 0. Opensips doesn't provide a way to isolate a branch ID from the via that I could find. Possibly something can be done with regular expressions. I hate those things. Opensips does provide the following --
1.4.6. extra_id_pv (string)
The parameter sets the PV definition to use when the “via-branch=extra” option is used on the rtpengine_delete(), rtpengine_offer(), rtpengine_answer() or rtpengine_manage() commands.

Default is empty, the “via-branch=extra” option may not be used then.

Example 1.6. Set extra_id_pv parameter

...
modparam("rtpengine", "extra_id_pv", "$avp(extra_id)")
...
So I assigned the branch ID to that pseudo var and passed it to rtpengine. Then rtpengine was able handle forks properly. Perhaps I could eliminate "extra-id" when handling in-dialog requests and responses. As you suggest perhaps some arbitrary value could be used. I will give it some thought. I am still perplexed as to why the problem was one particular host and only when it was the callee for the original request.

Regarding "ICE=optional", that was an experiment as I had originally used "ICE=force". It didn't seem to make a difference.

Robert Dyck

unread,
Dec 13, 2021, 6:19:28 PM12/13/21
to rtpengine
I revisited the branch ID problem. I had previously looked for an opensips function simialr to the check_route_param but for via. However opensips has a number of operators called transformations. So either {via.param,name} the general case or {via.branch}, the specific case transformation should return the value.

Richard Fuchs

unread,
Dec 13, 2021, 6:31:40 PM12/13/21
to rtpe...@googlegroups.com
On 13/12/2021 14.09, [EXT] Robert Dyck wrote:
> When I migrated to rtpengine from rtpproxy I had trouble with forked
> calls. By adding the branch ID ( not the via branch parameter but an
> integer 0 to whatever ) the  problem with forked calls was resolved.
> Of course for in-dialog requests it will always be 0. Opensips doesn't
> provide a way to isolate a branch ID from the via that I could find.
> Possibly something can be done with regular expressions. I hate those
> things. Opensips does provide the following --
> 1.4.6. extra_id_pv (string)
> The parameter sets the PV definition to use when the
> “via-branch=extra” option is used on the rtpengine_delete(),
> rtpengine_offer(), rtpengine_answer() or rtpengine_manage() commands.
>
> Default is empty, the “via-branch=extra” option may not be used then.

According to the OpenSIPS docs, it also supports the `=1` and `=2` and
`=auto` values for that parameter, which should do what you want.

Where is the `0` value coming from? Is that something you explicitly set
in your config script? Do `=1` or `=2` or `=auto` not work? Perhaps this
is a bug in the OpenSIPS module.

Or if it is indeed only a problem with in-dialogue requests and you can
distinguish them, you can omit the `via-branch` setting altogether. It's
only needed for initial forked offers.

Cheers

Robert Dyck

unread,
Dec 16, 2021, 11:59:22 PM12/16/21
to rtpengine
Sorry about the delay. Life happens.
First I need to clarify something I mentioned earlier. I thought the Firefox problem was on one particular host but I woke up and realized I hadn't tested FF on another host. Anyway FF fails regardless, saying it didn't like the ICE restart. That makes more sense.
I did some more testing with Chrome. Although it doesn't complain about the ICE restart and seems to handle the Hold and Resume OK in fact we are left with one way audio.

I tried your suggestions. First I used the actual via branch Ids rather than the branch index. This did not alter the behaviour at all. I tried "auto"
 and this caused a server error, unable to create branches. A value of 1 just behaves as before with one way audio. For those last two I eliminated "extra_id_pv (string)" from the module parameters. That is the default. I didn't try a forked call. As I said before putting the branch index into extra_id was needed to make forking work.
I also added logic in the opensips script to only force ICE when establishing the dialog but leave it alone while in dialog. This didn't help either.

After the ICE restart and additional relay port gets thrown into the mix.

Richard Fuchs

unread,
Dec 17, 2021, 9:49:40 AM12/17/21
to rtpe...@googlegroups.com
On 16/12/2021 23.59, [EXT] Robert Dyck wrote:
> Sorry about the delay. Life happens.
> First I need to clarify something I mentioned earlier. I thought the
> Firefox problem was on one particular host but I woke up and realized
> I hadn't tested FF on another host. Anyway FF fails regardless, saying
> it didn't like the ICE restart. That makes more sense.
> I did some more testing with Chrome. Although it doesn't complain
> about the ICE restart and seems to handle the Hold and Resume OK in
> fact we are left with one way audio.
>
> I tried your suggestions. First I used the actual via branch Ids
> rather than the branch index. This did not alter the behaviour at all.
> I tried "auto"
>  and this caused a server error, unable to create branches. A value of
> 1 just behaves as before with one way audio. For those last two I
> eliminated "extra_id_pv (string)" from the module parameters. That is
> the default. I didn't try a forked call. As I said before putting the
> branch index into extra_id was needed to make forking work.

Confirm in the rtpengine log files the value of the `via-branch`
parameter that actually gets passed to it from OpenSIPS. Make sure the
value from the initial A → B offer is not the same as the value from the
reversed B → A re-offer. The value used in each answer must match the
corresponding offer. So you should see something like this:

A → B offer: via-branch=foo
B → A answer: via-branch=foo

B → A re-offer: via-branch=bar
A → B answer: via-branch=bar

Cheers


Robert Dyck

unread,
Dec 19, 2021, 1:59:34 PM12/19/21
to rtpengine
Because I had made some changes in the config script I did some testing to make sure I hadn't introduced some problem. I tested forked calls including calls that involved rtpengine. I tested NAT'd calls and calls that mixed address families. Re-invites were tested in those scenarios. I reverted the changes I had tried with exception of the via branch ID. So that parameter that is passed to rtpengine is the branch ID obtained from the incoming offer rather than the branch index that I had used previously.

I am attaching another file. The previous one involved Firefox which simply dropped the call when it saw the ICE restart. This one is with Chrome which tries to carry on despite the restart. After re-offer ( hold ) and re-offer ( resume ) we are left with one way audio. This time rather than including the sdp from the rtpengine logs I am including the complete messages coming into opensips and leaving. From the rtpengine logs are the messages it got from opensips and also the ICE negotiation stuff.

During the testing I looked at the media flow with Wireshark. In the commentary I showed the relay ports that rtpengine was using to relay the media.

The test in the attached file was a forked call. It wasn't intentional but there happened to be two UAs registered with the same AOR. I noticed that when opensips added its own via it suffixed the branch ID with an integer like abcxyz.0 and abcxyz.1. I am assuming the suffix is the opensips branch index. In this particular instance the answer came from index 1.

rtpengine-on-hold-problem.txt

Richard Fuchs

unread,
Dec 20, 2021, 9:16:25 AM12/20/21
to rtpe...@googlegroups.com
On 19/12/2021 13.59, [EXT] Robert Dyck wrote:
Because I had made some changes in the config script I did some testing to make sure I hadn't introduced some problem. I tested forked calls including calls that involved rtpengine. I tested NAT'd calls and calls that mixed address families. Re-invites were tested in those scenarios. I reverted the changes I had tried with exception of the via branch ID. So that parameter that is passed to rtpengine is the branch ID obtained from the incoming offer rather than the branch index that I had used previously.

I am attaching another file. The previous one involved Firefox which simply dropped the call when it saw the ICE restart. This one is with Chrome which tries to carry on despite the restart. After re-offer ( hold ) and re-offer ( resume ) we are left with one way audio. This time rather than including the sdp from the rtpengine logs I am including the complete messages coming into opensips and leaving. From the rtpengine logs are the messages it got from opensips and also the ICE negotiation stuff.

During the testing I looked at the media flow with Wireshark. In the commentary I showed the relay ports that rtpengine was using to relay the media.

The test in the attached file was a forked call. It wasn't intentional but there happened to be two UAs registered with the same AOR. I noticed that when opensips added its own via it suffixed the branch ID with an integer like abcxyz.0 and abcxyz.1. I am assuming the suffix is the opensips branch index. In this particular instance the answer came from index 1.

It's a bit hard to tell with the truncated/mixed log, but AFAICT the `via-branch` values are all different.

A → B offer: via-branch=z9hG4bKtvnyaxfi -- OK
B → A answer: via-branch=z9hG4bKc05a.b6df8e45.1 -- not OK, this should be z9hG4bKtvnyaxfi

B → A re-offer: via-branch=z9hG4bK5128999 -- OK
A → B answer: via-branch=z9hG4bKe02e.0e0d6b77.0 -- not OK, this should be z9hG4bK5128999

In particular it's that last one that makes rtpengine produce a new set of ICE attributes because it thinks the answer is going to a different endpoint (not B).

Below is what the log looks like with correct usage of via-branch tags (but otherwise identical SDPs etc). As you can see the ICE attributes come out the same.

A → B offer

[1640009319.758562] DEBUG: [9192]: [control] Dump for 'offer' from 127.0.0.1:41313: { "DTLS-fingerprint": "sha-256", "ICE": "force", "SDES": [ "off" ], "call-id": "9192", "command": "offer", "flags": [ "generate mid" ], "from-tag": "foo", "rtcp-mux": [ "require" ], "sdp": "v=0
o=twinkle 1739380060 14469997 IN IP4 192.168.1.3
s=-
c=IN IP4 192.168.1.3
t=0 0
m=audio 8000 RTP/AVP 9 0 98 97 8 3 18
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:98 speex/16000
a=rtpmap:97 speex/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:3 GSM/8000
a=rtpmap:18 G729/8000
a=fmtp:18 annexb=no
a=ptime:20
", "transport protocol": "UDP/TLS/RTP/SAVPF", "via-branch": "branch-one" }

[1640009319.760323] DEBUG: [9192]: [control] Response dump for 'offer' to 127.0.0.1:41313: { "sdp": "v=0
o=twinkle 1739380060 14469997 IN IP4 192.168.1.3
s=-
c=IN IP4 192.168.1.87
t=0 0
m=audio 30000 UDP/TLS/RTP/SAVPF 9 0 98 97 8 3 18
a=mid:1
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:98 speex/16000
a=rtpmap:97 speex/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:3 GSM/8000
a=rtpmap:18 G729/8000
a=fmtp:18 annexb=no
a=sendrecv
a=rtcp:30000
a=rtcp-mux
a=setup:actpass
a=fingerprint:sha-256 D9:10:52:C6:1E:90:C3:9A:AA:1D:F8:B3:21:71:7B:4B:A7:90:4C:57:F7:35:05:24:F6:44:32:9F:EE:83:4E:14
a=ptime:20
a=ice-ufrag:6BbtP8f1
a=ice-pwd:pjE2JLJiubqNcT8GQgFnN4Ercb
a=candidate:kWKBQ7FGYBqfmVsE 1 UDP 2130706431 192.168.1.87 30000 typ host
a=candidate:fNwLkELnT1ovyK8n 1 UDP 2130706175 2608:fea8:ab00:33::a38 30000 typ host
a=candidate:yEid4l1t2baw4PHS 1 UDP 2130705919 2608:fea8:ab00:33:26da:916f:ef1:1451 30000 typ host
", "result": "ok" }

B → A answer

[1640009322.421105] DEBUG: [9192]: [control] Dump for 'answer' from 127.0.0.1:38501: { "DTLS-fingerprint": "sha-256", "ICE": "remove", "call-id": "9192", "command": "answer", "from-tag": "foo", "rtcp-mux": [ "demux" ], "sdp": "v=0
o=- 6983899188004734691 2 IN IP4 127.0.0.1
s=-
t=0 0
a=msid-semantic: WMS UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S
m=audio 56934 UDP/TLS/RTP/SAVPF 9 0 8
c=IN IP4 192.168.1.3
a=rtcp:9 IN IP4 0.0.0.0
a=candidate:3416155287 1 udp 2122262783 2001::f58f 45779 typ host generation 0 network-id 2
a=candidate:704553097 1 udp 2122194687 192.168.1.3 56934 typ host generation 0 network-id 1
a=ice-ufrag:YZ3J
a=ice-pwd:2SkZrILTdAoEGWtgZT3FJjsb
a=ice-options:trickle
a=fingerprint:sha-256 A1:D3:50:31:64:0A:2A:C3:39:12:22:32:8A:B1:D9:C8:AB:43:50:30:78:D6:EF:0C:E5:83:37:51:8D:46:BA:F9
a=setup:active
a=mid:1
a=sendrecv
a=msid:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S 56191062-a8f5-4d97-a903-d233bb6586ed
a=rtcp-mux
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=ssrc:1991661435 cname:upYUcx8RyTC4x1lp
a=ssrc:1991661435 msid:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S 56191062-a8f5-4d97-a903-d233bb6586ed
a=ssrc:1991661435 mslabel:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S
a=ssrc:1991661435 label:56191062-a8f5-4d97-a903-d233bb6586ed
", "to-tag": "bar", "transport protocol": "RTP/AVP", "via-branch": "branch-one" }

[1640009322.421622] DEBUG: [9192]: [control] Response dump for 'answer' to 127.0.0.1:38501: { "sdp": "v=0
o=- 6983899188004734691 2 IN IP4 127.0.0.1
s=-
t=0 0
a=msid-semantic: WMS UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S
m=audio 30018 RTP/AVP 9 0 8
c=IN IP4 192.168.1.87
a=msid:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S 56191062-a8f5-4d97-a903-d233bb6586ed
a=ssrc:1991661435 cname:upYUcx8RyTC4x1lp
a=ssrc:1991661435 msid:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S 56191062-a8f5-4d97-a903-d233bb6586ed
a=ssrc:1991661435 mslabel:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S
a=ssrc:1991661435 label:56191062-a8f5-4d97-a903-d233bb6586ed
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=sendrecv
a=rtcp:30019
a=ptime:20
", "result": "ok" }

B → A re-offer

[1640009324.804649] DEBUG: [9192]: [control] Dump for 'offer' from 127.0.0.1:59072: { "ICE": "remove", "call-id": "9192", "command": "offer", "from-tag": "bar", "rtcp-mux": [ "demux" ], "sdp": "v=0
o=- 6983899188004734691 3 IN IP4 127.0.0.1
s=-
t=0 0
a=extmap-allow-mixed
a=msid-semantic: WMS UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S
a=group:BUNDLE 1
m=audio 56934 UDP/TLS/RTP/SAVPF 9 0 8 111 63 103 104 106 105 13 110 112 113 126
c=IN IP4 192.168.1.3
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:111 opus/48000/2
a=rtpmap:63 red/48000/2
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=fmtp:111 minptime=10;useinbandfec=1
a=fmtp:63 111/111
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-fb:111 transport-cc
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=setup:actpass
a=mid:1
a=msid:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S 56191062-a8f5-4d97-a903-d233bb6586ed
a=sendonly
a=ice-ufrag:YZ3J
a=ice-pwd:2SkZrILTdAoEGWtgZT3FJjsb
a=fingerprint:sha-256 A1:D3:50:31:64:0A:2A:C3:39:12:22:32:8A:B1:D9:C8:AB:43:50:30:78:D6:EF:0C:E5:83:37:51:8D:46:BA:F9
a=candidate:3416155287 1 udp 2122262783 2001::f58f 45779 typ host generation 0 network-id 2
a=candidate:704553097 1 udp 2122194687 192.168.1.3 56934 typ host generation 0 network-id 1
a=ice-options:trickle
a=ssrc:1991661435 cname:upYUcx8RyTC4x1lp
a=ssrc:1991661435 msid:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S 56191062-a8f5-4d97-a903-d233bb6586ed
a=ssrc:1991661435 mslabel:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S
a=ssrc:1991661435 label:56191062-a8f5-4d97-a903-d233bb6586ed
a=rtcp-mux
", "to-tag": "foo", "transport protocol": "RTP/AVP", "via-branch": "branch-two" }

[1640009324.805710] DEBUG: [9192]: [control] Response dump for 'offer' to 127.0.0.1:59072: { "sdp": "v=0
o=- 6983899188004734691 3 IN IP4 127.0.0.1
s=-
t=0 0
a=extmap-allow-mixed
a=msid-semantic: WMS UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S
m=audio 30018 RTP/AVP 9 0 8 111 63 103 104 106 105 13 110 112 113 126
c=IN IP4 192.168.1.87
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=msid:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S 56191062-a8f5-4d97-a903-d233bb6586ed
a=ssrc:1991661435 cname:upYUcx8RyTC4x1lp
a=ssrc:1991661435 msid:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S 56191062-a8f5-4d97-a903-d233bb6586ed
a=ssrc:1991661435 mslabel:UNyBsn7zTPvf0iTi2G9IgA4wVMAfKKAq412S
a=ssrc:1991661435 label:56191062-a8f5-4d97-a903-d233bb6586ed
a=mid:1
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
a=rtcp-fb:111 transport-cc
a=rtpmap:63 red/48000/2
a=fmtp:63 111/111
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=sendonly
a=rtcp:30019
a=ptime:20
", "result": "ok" }

A → B answer

[1640009326.906187] DEBUG: [9192]: [control] Dump for 'answer' from 127.0.0.1:47949: { "DTLS-fingerprint": "sha-256", "ICE": "force", "SDES": [ "off" ], "call-id": "9192", "command": "answer", "flags": [ "generate mid" ], "from-tag": "bar", "rtcp-mux": [ "require" ], "sdp": "v=0
o=twinkle 1739380060 14469998 IN IP4 192.168.1.3
s=-
c=IN IP4 192.168.1.3
t=0 0
m=audio 8000 RTP/AVP 9
a=rtpmap:9 G722/8000
a=recvonly
", "to-tag": "foo", "transport protocol": "UDP/TLS/RTP/SAVPF", "via-branch": "branch-two" }

[1640009326.906475] DEBUG: [9192]: [control] Response dump for 'answer' to 127.0.0.1:47949: { "sdp": "v=0
o=twinkle 1739380060 14469998 IN IP4 192.168.1.3
s=-
c=IN IP4 192.168.1.87
t=0 0
m=audio 30000 UDP/TLS/RTP/SAVPF 9
a=mid:1
a=rtpmap:9 G722/8000
a=recvonly
a=rtcp:30000
a=rtcp-mux
a=setup:passive
a=fingerprint:sha-256 D9:10:52:C6:1E:90:C3:9A:AA:1D:F8:B3:21:71:7B:4B:A7:90:4C:57:F7:35:05:24:F6:44:32:9F:EE:83:4E:14
a=ptime:20
a=ice-ufrag:6BbtP8f1
a=ice-pwd:pjE2JLJiubqNcT8GQgFnN4Ercb
a=ice-options:trickle
a=candidate:kWKBQ7FGYBqfmVsE 1 UDP 2130706431 192.168.1.87 30000 typ host
a=candidate:fNwLkELnT1ovyK8n 1 UDP 2130706175 2608:fea8:ab00:33::a38 30000 typ host
a=candidate:yEid4l1t2baw4PHS 1 UDP 2130705919 2608:fea8:ab00:33:26da:916f:ef1:1451 30000 typ host
a=end-of-candidates
", "result": "ok" }

Robert Dyck

unread,
Dec 20, 2021, 1:00:37 PM12/20/21
to rtpengine
A → B offer: via-branch=z9hG4bKtvnyaxfi -- OK
B → A answer: via-branch=z9hG4bKc05a.b6df8e45.1 -- not OK, this should be z9hG4bKtvnyaxfi

Why is this not OK. The initial ofer/answer was working fine. From the proxy's perspective this is how it has to be. The branch ID is actually a transaction ID. The proxy added this before it sent the message out. When the answer comes back the proxy looks at the top via branch and sees that it has pending tranaction that matches. The tranaction is satisfied and the proxy removes its via and passes the answer on. The caller sees the remaining via and recognizes its transaction.

I notice that your example flow does not include via-branch in any of the commands to rtpengine.

Richard Fuchs

unread,
Dec 20, 2021, 1:42:35 PM12/20/21
to rtpe...@googlegroups.com
On 20/12/2021 13.00, [EXT] Robert Dyck wrote:
> A → B offer: via-branch=z9hG4bKtvnyaxfi -- OK
> B → A answer: via-branch=z9hG4bKc05a.b6df8e45.1 -- not OK, this should
> be z9hG4bKtvnyaxfi
>
> Why is this not OK. The initial ofer/answer was working fine. From the
> proxy's perspective this is how it has to be. The branch ID is
> actually a transaction ID. The proxy added this before it sent the
> message out. When the answer comes back the proxy looks at the top via
> branch and sees that it has pending tranaction that matches. The
> tranaction is satisfied and the proxy removes its via and passes the
> answer on. The caller sees the remaining via and recognizes its
> transaction.
It doesn't matter what the proxy does, and it doesn't matter what's in
the SIP headers. What matters is what rtpengine gets told by the proxy,
and in order to match an answer to the corresponding offer, the
via-branch value has to be the same. If you have an offer branched to
two clients, the two offers would have via-branch=A and =B. The answer
then needs to also list via-branch=A or =B so rtpengine knows which
offer the answer belongs to. (This is why `via-branch=auto` exists,
because under normal circumstances this makes the proxy use the correct
matching via-branch tags for both offers and answers.)
>
> I notice that your example flow does not include via-branch in any of
> the commands to rtpengine.

Uhm, yes it does? I even highlighted them. The line containing the
via-branch key is right after the SDP.

Cheers

Robert Dyck

unread,
Dec 20, 2021, 2:04:35 PM12/20/21
to rtpengine
Oh, at the very end. I thought you had added that yourself. How is "branch-one"  generated?

Richard Fuchs

unread,
Dec 20, 2021, 2:11:53 PM12/20/21
to rtpe...@googlegroups.com
On 20/12/2021 14.04, [EXT] Robert Dyck wrote:
Oh, at the very end. I thought you had added that yourself. How is "branch-one"  generated?

It isn't. I made it up. As I explained before, it doesn't really matter what the value for `via-branch` is, as long as it's unique for offers going to different recipients, and as long as answers can be matched to offers. Using the `branch=` tag from `Via` is a convenient way to achieve this if you use the right header, e.g. the first one for requests and the second one for responses.

INVITE sip:6...@192.168.1.2 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.3;rport;branch=z9hG4bKtvnyaxfi
Max-Forwards: 70
SIP/2.0 200 OK
Record-Route: <sip:[2001::D2B4]:8000;transport=wss;r2=on;lr;ftag=idgzl;mixed-AF;mixed-RTC>
Record-Route: <sip:192.168.1.2;r2=on;lr;ftag=idgzl;mixed-AF;mixed-RTC>
Via: SIP/2.0/WSS [2001::D2B4]:8000;branch=z9hG4bKc05a.b6df8e45.1
Via: SIP/2.0/UDP 192.168.1.3;received=192.168.1.3;rport=5060;branch=z9hG4bKtvnyaxfi
Cheers

Robert Dyck

unread,
Dec 20, 2021, 2:38:58 PM12/20/21
to rtpengine
I didn't have success with "auto" before but I can revisit that. I can try adding "auto" directly to the rtpengine command or using  the pseudo-var modparam("rtpengine", "extra_id_pv", "$avp(extra_id)"). I get what you're saying but my existing configuration works fine in situations where rtpengine is required but ICE is not required including forked calls. And then there is the fact that Firefox simply drops the second offer and complains about ICE.
For the moment other things need doing.

Robert Dyck

unread,
Jan 3, 2022, 1:06:37 PM1/3/22
to rtpengine
Happy New Year
I have been trying to dive a little deeper into the via-branch thing. For opensips the "via-branch=auto" option is documented but it doesn't appear to be implemented.

Jan  1 09:35:40 [2665281] profile is  debug via-branch=auto ICE=remove RTP/AVP replace-session-connection replace-origin rtcp-mux-demux
Jan  1 09:35:40 [2665281] now set the interfaces
Jan  1 09:35:40 [2665281] add route parameters to help with sequential INVITEs
Jan  1 09:35:40 [2665281] Deal with mixed address families
Jan  1 09:35:40 [2665281] DBG:rr:add_rr_param: adding (;mixed-AF)
Jan  1 09:35:40 [2665281] DBG:rr:add_rr_param: second RR lump found
Jan  1 09:35:40 [2665281] DBG:rr:add_rr_param: second RR lump found
Jan  1 09:35:40 [2665281] DBG:core:parse_headers: flags=ffffffffffffffff
Jan  1 09:35:40 [2665281] DBG:core:decode_mime_type: Decoding MIME type for:[application/sdp]
Jan  1 09:35:40 [2665281] DBG:core:parse_headers: flags=40
Jan  1 09:35:40 [2665281] ERROR:rtpengine:rtpe_function_call: can't get Via branch/extra ID
Jan  1 09:35:40 [2665281] rtpengine_offer failed
Jan  1 09:35:40 [2665281] DBG:tm:pre_print_uac_request: dropping branch <sip:7...@192.168.1.87>
This seems to suggest that "via-branch=extra" is not working but it is.

Even if the auto option was working it would be the same as the 1/2 option. The 1/2 option doesn't seem to serve any purpose and in fact causes a problem with forked calls. Rtpeengine prepares a relay dialogue when it gets an offer and is only missing the endpoint of the successful answer. With the 1/2 option the via-branch ID is passed to rtpengine. If the call is forked each offer contains the same value for via-branch. Even though the call may have been forked only a single dialogue is built. Each branch may have different requirements for the relay. Depending on the order the branches and their requirements are added and who is the answerer, some calls fail.

That is the reason I started I started using "via-branch=extra" with extra value being the branch index. It solved my problem with initial call setup. Rtpengine then prepared multiple dialogues and when the answer comes the correct requirements are applied.

This introduced more trouble. All the dialogues remained up for the duration of the call even though only one was actually in use. A possible solution I tried was to use the 487 response from the unsuccessful callees and using the branch index as before, delete the dialogue. What if one of unsuccessful callees didn't actually require media relay. That branch would be unknown to rtpengine. It actually results in all the dialogues being torn down. When the 487 response is received there is no way of knowing if there was a relay dialogue associated with it.

Dec 30 09:17:47 slim rtpengine[1356291]: DEBUG: [9dhthqb47rgqp8vg6elo]: [core] Destroying monologue 'apq2bhajd2' ()
Dec 30 09:17:47 slim rtpengine[1356291]: DEBUG: [9dhthqb47rgqp8vg6elo]: [core] Destroying monologue 'mitoh' (0)
Dec 30 09:17:47 slim rtpengine[1356291]: DEBUG: [9dhthqb47rgqp8vg6elo]: [core] Destroying monologue '' (1)
Dec 30 09:17:47 slim rtpengine[1356291]: DEBUG: [9dhthqb47rgqp8vg6elo]: [core] Destroying monologue '' (2)
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] Call branch 'apq2bhajd2' (via-branch '') deleted, no more branches remaining
Dec 30 09:17:47 slim rtpengine[1356291]: DEBUG: [9dhthqb47rgqp8vg6elo]: [core] Redis delete_async=0
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] Final packet stats:
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --- Tag 'apq2bhajd2', created 0:13 ago for branch '', in dialogue with 'mitoh'
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] ------ Media #1 (audio over UDP/TLS/RTP/SAVPF) using G722/8000
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --------- Port 2001::d2b4:35014 <> 2001:5:f58f:42362, SSRC 7a798a6a, 75 p, 12492 b, 0 e, 10 ts
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --- Tag 'mitoh', created 0:13 ago for branch '0', in dialogue with 'apq2bhajd2'
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] ------ Media #1 (audio over RTP/AVP) using G722/8000
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --------- Port     192.168.1.1:35014 <>     192.168.1.3:8000 , SSRC 83b39d7e, 75 p, 12900 b, 0 e, 10 ts
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --------- Port     192.168.1.1:35015 <>     192.168.1.3:8001  (RTCP), SSRC 83b39d7e, 2 p, 76 b, 0 e, 10 ts
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --- Tag '', created 0:13 ago for branch '1', in dialogue with 'apq2bhajd2'
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] ------ Media #1 (audio over RTP/AVP) using unknown codec
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --------- Port     192.168.1.1:35022 <>                :0    , SSRC 0, 0 p, 0 b, 0 e, 13 ts
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --------- Port     192.168.1.1:35023 <>                :0     (RTCP), SSRC 0, 0 p, 0 b, 0 e, 13 ts
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --- Tag '', created 0:13 ago for branch '2', in dialogue with 'apq2bhajd2'
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] ------ Media #1 (audio over RTP/AVP) using unknown codec
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --------- Port 2001::d2b4:35000 <>                :0    , SSRC 0, 0 p, 0 b, 0 e, 13 ts
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] --------- Port 2001::d2b4:35001 <> 

From the phone's point of view nothing has happened until they get an rtp timeout.

And then there a problem with subsequent offers. When there is a re-invite within a SIP dialogue the branch index is always 0 ( no fork ). The actual working branch from rtpengine's point of view may be 1 or 2 or whatever. Is this the cause of the ICE restarts I am seeing when a call is held and resumed.

Richard Fuchs

unread,
Jan 3, 2022, 1:29:04 PM1/3/22
to rtpe...@googlegroups.com
On 03/01/2022 13.06, [EXT] Robert Dyck wrote:
Even if the auto option was working it would be the same as the 1/2 option. The 1/2 option doesn't seem to serve any purpose and in fact causes a problem with forked calls. Rtpeengine prepares a relay dialogue when it gets an offer and is only missing the endpoint of the successful answer. With the 1/2 option the via-branch ID is passed to rtpengine. If the call is forked each offer contains the same value for via-branch.
This is what would happen if the invocation of rtpengine happens before the branching. The 1/2 option takes the branch tag from the existing Via headers in the SIP message, so that only works if the branches have already been created at that point. In Kamailio we have `via-branch=next` for this reason, which uses the "next" branch tag generated by Kamailio (to be used in outgoing SIP messages), which would then match the tag in the received responses.

That is the reason I started I started using "via-branch=extra" with extra value being the branch index. It solved my problem with initial call setup. Rtpengine then prepared multiple dialogues and when the answer comes the correct requirements are applied.

This introduced more trouble. All the dialogues remained up for the duration of the call even though only one was actually in use. A possible solution I tried was to use the 487 response from the unsuccessful callees and using the branch index as before, delete the dialogue. What if one of unsuccessful callees didn't actually require media relay. That branch would be unknown to rtpengine. It actually results in all the dialogues being torn down. When the 487 response is received there is no way of knowing if there was a relay dialogue associated with it.

Dec 30 09:17:47 slim rtpengine[1356291]: DEBUG: [9dhthqb47rgqp8vg6elo]: [core] Destroying monologue 'apq2bhajd2' ()
Dec 30 09:17:47 slim rtpengine[1356291]: DEBUG: [9dhthqb47rgqp8vg6elo]: [core] Destroying monologue 'mitoh' (0)
Dec 30 09:17:47 slim rtpengine[1356291]: DEBUG: [9dhthqb47rgqp8vg6elo]: [core] Destroying monologue '' (1)
Dec 30 09:17:47 slim rtpengine[1356291]: DEBUG: [9dhthqb47rgqp8vg6elo]: [core] Destroying monologue '' (2)
Dec 30 09:17:47 slim rtpengine[1356291]: INFO: [9dhthqb47rgqp8vg6elo]: [core] Call branch 'apq2bhajd2' (via-branch '') deleted, no more branches remaining

I can't tell what exactly happened here since you didn't post the actual signalling (just the result), but in general: Any branch you create in rtpengine you also have to delete in the same way. If you delete a branch based on the from-tag alone, then all branches connected to that from-tag will be deleted (which I suspect is what happened here). If you want to delete only one branch, you need to use the appropriate via-branch (or to-tag if there is one). Deleting branches is directional, so order of the tags is important. Omitting the from-tag altogether would delete the entire call, all tags, all branches.

Cheers

Robert Dyck

unread,
Jan 3, 2022, 2:43:27 PM1/3/22
to rtpengine
Here is the signalling. Opensips would have added the various tags and the "received from" address. The script only adds via-branch=extra which would the branch index. I remember this instance. The branch that didn't need rtpengine happened to be 0. I think that  "Destroying monologue 'mitoh' (0)" is just rtpengine's order not the branch index that was passed. So the 0 here would have been branch index 1. The second branch but the first one requiring rtpengine. The point here is that there was no way of knowing that branch 0 did not have a relay dialogue associated with it.

         xlog("checking for 487 status\n");
        if (t_check_status("487")) {
                xlog("A request was terminated, send delete for branch $T_branch_idx\n");
                xlog("Branch is $T_branch_idx\n");
#                rtpengine_delete(" via-branch=extra");
                return;

Opensips does not document "next" and I haven't tried it.

Via-branch is created by the caller's device and is exposed by the 1 option with offer and is exposed by the 2 option with an answer and will be identical for all branches created by opensips ( provided there are no intervening proxies ). The only way I see to create multiple relay dialogues is to send unique ID's to rtpengine. Like I said doing that solved the issues I had with forked calls at least for initial offers. Here we see that the via-branch is the same for each Opensips branch. The monologues is created once and used by each branch.
First offer
"ICE": "remove", "direction": [ "ipv6", "ipv4-priv" ], "flags": [ "debug" ], "replace": [ "session-connection", "origin" ], "transport-protocol": "RTP/AVP", "rtcp-mux": [ "demux" ], "call-id": "s25p40fpr5g0u52b96dp", "via-branch": "z9hG4bK3119290", "received-from": [ "IP6", "2001::F58F" ], "from-tag": "as1g4gcnjp", "command": "offer" }
Jan  1 10:03:54 slim rtpengine[2517903]: NOTICE: [s25p40fpr5g0u52b96dp]: [core] Creating new call
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] getting monologue for tag 'as1g4gcnjp' in call 's25p40fpr5g0u52b96dp'
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] creating new monologue
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] tagging monologue with 'as1g4gcnjp'
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] create new "other side" monologue for viabranch z9hG4bK3119290
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] creating new monologue
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] tagging monologue with viabranch 'z9hG4bK3119290'
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] this= other=as1g4gcnjp

Second offer
"ICE": "remove", "direction": [ "ipv6", "ipv4-priv" ], "flags": [ "debug" ], "replace": [ "session-connection", "origin" ], "transport-protocol": "RTP/AVP", "rtcp-mux": [ "demux" ], "call-id": "s25p40fpr5g0u52b96dp", "via-branch": "z9hG4bK3119290", "received-from": [ "IP6", "2001::F58F" ], "from-tag": "as1g4gcnjp", "command": "offer" }
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] getting monologue for tag 'as1g4gcnjp' in call 's25p40fpr5g0u52b96dp'
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] found existing monologue
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] this= other=as1g4gcnjp

Third offer
 "ICE": "force", "DTLS-fingerprint": "sha-256", "direction": [ "ipv4-priv", "ipv4-ext" ], "flags": [ "debug", "SDES-off", "generate-mid" ], "replace": [ "session-connection", "origin" ], "transport-protocol": "UDP/TLS/RTP/SAVPF", "rtcp-mux": [ "require" ], "call-id": "s25p40fpr5g0u52b96dp", "via-branch": "z9hG4bK3119290", "received-from": [ "IP6", "2001::F58F" ], "from-tag": "as1g4gcnjp", "command": "offer" }
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] getting monologue for tag 'as1g4gcnjp' in call 's25p40fpr5g0u52b96dp'
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] found existing monologue
Jan  1 10:03:54 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] this= other=as1g4gcnjp

The answer
"ICE": "force", "DTLS-fingerprint": "sha-256", "direction": [ "ipv4-priv", "ipv6" ], "flags": [ "debug", "SDES-off", "generate-mid" ], "replace": [ "session-connection", "origin" ], "transport-protocol": "UDP/TLS/RTP/SAVPF", "rtcp-mux": [ "require" ], "call-id": "s25p40fpr5g0u52b96dp", "via-branch": "z9hG4bK3119290", "received-from": [ "IP4", "192.168.1.87" ], "from-tag": "as1g4gcnjp", "to-tag": "kvhgy", "command": "answer" }
Jan  1 10:03:57 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] getting dialogue for tags 'kvhgy'<>'as1g4gcnjp' in call 's25p40fpr5g0u52b96dp'
Jan  1 10:03:57 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] tagging monologue with 'kvhgy'
Jan  1 10:03:57 slim rtpengine[2517903]: DEBUG: [s25p40fpr5g0u52b96dp]: [internals] this=as1g4gcnjp other=kvhgy

In this particular call  "rtcp-mux" did not appear in the sdp presented to the caller which was a mandatory attribute. The call failed. Why did this happen? I assume because rtpengine stacked multiple rquirements on each other.

How can this work reliably with different requirements for different branches?

Richard Fuchs

unread,
Jan 3, 2022, 3:40:49 PM1/3/22
to rtpe...@googlegroups.com
On 03/01/2022 14.43, [EXT] Robert Dyck wrote:
Here is the signalling. Opensips would have added the various tags and the "received from" address. The script only adds via-branch=extra which would the branch index. I remember this instance. The branch that didn't need rtpengine happened to be 0. I think that  "Destroying monologue 'mitoh' (0)" is just rtpengine's order not the branch index that was passed. So the 0 here would have been branch index 1. The second branch but the first one requiring rtpengine. The point here is that there was no way of knowing that branch 0 did not have a relay dialogue associated with it.

         xlog("checking for 487 status\n");
        if (t_check_status("487")) {
                xlog("A request was terminated, send delete for branch $T_branch_idx\n");
                xlog("Branch is $T_branch_idx\n");
#                rtpengine_delete(" via-branch=extra");
                return;

That's the script doing the deletion, not the log of the signalling going to rtpengine. The log you posted below shows the call setup, not the call deletion. Also it looks different from your last email, which used branch tags "0", "1", and "2", while this one uses the actual branch tag from the Via header (although always the same one).

Using branch tags "0", "1", and "2" would be correct: different tags for different branches. The tags used in the answer and the deletions must match those tags. I don't know if this is the case since you didn't post it.

The log below shows the same branch tag used for all branches, which is not correct. From rtpengine's point there were no branches: it was simply a repeated offer going to the same endpoint, but with different options. (This is similar to the original issue you posted about, with the same branch tag used in the reverse re-invite as in the original forward invite.)

(BTW, things would be a lot easier without constantly mixing different issues and different scenarios.)

"ICE": "remove", "direction": [ "ipv6", "ipv4-priv" ], "flags": [ "debug" ], "replace": [ "session-connection", "origin" ], "transport-protocol": "RTP/AVP", "rtcp-mux": [ "demux" ], "call-id": "s25p40fpr5g0u52b96dp", "via-branch": "z9hG4bK3119290", "received-from": [ "IP6", "2001::F58F" ], "from-tag": "as1g4gcnjp", "command": "offer" }
...

"ICE": "remove", "direction": [ "ipv6", "ipv4-priv" ], "flags": [ "debug" ], "replace": [ "session-connection", "origin" ], "transport-protocol": "RTP/AVP", "rtcp-mux": [ "demux" ], "call-id": "s25p40fpr5g0u52b96dp", "via-branch": "z9hG4bK3119290", "received-from": [ "IP6", "2001::F58F" ], "from-tag": "as1g4gcnjp", "command": "offer" }
...

 "ICE": "force", "DTLS-fingerprint": "sha-256", "direction": [ "ipv4-priv", "ipv4-ext" ], "flags": [ "debug", "SDES-off", "generate-mid" ], "replace": [ "session-connection", "origin" ], "transport-protocol": "UDP/TLS/RTP/SAVPF", "rtcp-mux": [ "require" ], "call-id": "s25p40fpr5g0u52b96dp", "via-branch": "z9hG4bK3119290", "received-from": [ "IP6", "2001::F58F" ], "from-tag": "as1g4gcnjp", "command": "offer" }
...

In this particular call  "rtcp-mux" did not appear in the sdp presented to the caller which was a mandatory attribute. The call failed. Why did this happen? I assume because rtpengine stacked multiple rquirements on each other.

How can this work reliably with different requirements for different branches?

Correct. It works reliably by using the appropriate branch tags (again). This creates branches within rtpengine and each branch can have different options. To give a full example as it would appear in the log:

A → B offer: from-tag=A via-branch=1 ...other-options-for-B...
A → C offer: from-tag=A via-branch=2 ...other-options-for-C...
A → D offer: from-tag=A via-branch=3 ...other-options-for-D...

(Neither the from/to tags nor the branch tags have to be literal "A" or "1" etc - those are just examples. They just need to be unique, and matching between offers and answers/deletes.)

C answers:

C → A answer: from-tag=A via-branch=2 to-tag=C ...other-options...
delete B: from-tag=A via-branch=1 (optionally to-tag=B)
delete D: from-tag=A via-branch=3 (optionally to-tag=D)

(The deletes can also come before the answer.)

Hang-up:

Either delete from A's side: from-tag=A
or from C's side: from-tag=C

(This is assuming that B, C, D are actually distinct endpoints that receive the branched offers, possibly simultaneously. If they're actually the same endpoint and you're just retrying the offer with different options, you can leave the branching as it is now, but make sure you delete the call in between invocations with `delete-delay=0` to force a complete reset of the internal state. This would be a somewhat different use case than actual branching.)

Cheers

Robert Dyck

unread,
Jan 3, 2022, 5:33:34 PM1/3/22
to rtpengine
Agreed. It works with unique IDs at least for initial offers. The example was for the 1/2 option which provides the same ID for each branch.

I reproduced the other scenario ( clear out the unused ports ). Unique via-branch IDs were passed to rtpengine in the offers so as to produce multiple relay dialogues. Delete commands were generated by the 487 type responses from the cancelled endpoints. The script added a via-branch ID corresponding to the Opensips branch the same way they were offered to rtpengine. Note that one of the deletes was generated from a branch that was never passed to rtpengine. This is because there is no information in the type 487 response to determine if the branch had been passed to rtpengine. This approach has been abondoned because as was the case here, the call failed after a short time due to rtp timeout. Delay = 0 was not used so the dialogue teardown was delayed.

Yeah there is a lot happening although closely related.
1) Create multiple relay dialogues to meet the unique requirements of each branch.
2)Release the ports that are not actually being used.
3) Handle re-offers which are always SIP branch 0 but the initial rtpengine setup used a different branch ID.

rtpengine-delete-unused-branches.txt

Robert Dyck

unread,
Jan 3, 2022, 8:26:34 PM1/3/22
to rtpengine
Just an aside, the via....branch=z9hG4bK...... name is unfortanate and misleading. It is not about forking ( from the caller's view, it's unaware ). It isn't even mandatory in rfc 2543. Abiding to rfc 3261 means for the moment, supporting rfc 2543. It is a transaction identifier used by a requestor to match a response to a request. It is of no interest to down stream devices/nodes. The rtpengine 1/2 options in practical terms are no different than providing no via-branch. That is where I started my rtpengine journey.

From rfc 3261 section 28.1
     The branch parameter of the Via header field value is now
      mandatory for all elements to use.  It now plays the role of a
      unique transaction identifier.  This avoids the complex and bug-
      laden transaction identification rules from RFC 2543.  A magic
      cookie is used in the parameter value to determine if the previous
      hop has made the parameter globally unique, and comparison falls
      back to the old rules when it is not present.  Thus,
      interoperability is assured.

Richard Fuchs

unread,
Jan 4, 2022, 8:29:29 AM1/4/22
to rtpe...@googlegroups.com
On 03/01/2022 17.33, [EXT] Robert Dyck wrote:
> Agreed. It works with unique IDs at least for initial offers. The
> example was for the 1/2 option which provides the same ID for each
> branch.
>
> I reproduced the other scenario ( clear out the unused ports ). Unique
> via-branch IDs were passed to rtpengine in the offers so as to produce
> multiple relay dialogues. Delete commands were generated by the 487
> type responses from the cancelled endpoints. The script added a
> via-branch ID corresponding to the Opensips branch the same way they
> were offered to rtpengine. Note that one of the deletes was generated
> from a branch that was never passed to rtpengine. This is because
> there is no information in the type 487 response to determine if the
> branch had been passed to rtpengine. This approach has been abondoned
> because as was the case here, the call failed after a short time due
> to rtp timeout. Delay = 0 was not used so the dialogue teardown was
> delayed.

Ok, I guess you mean these ones:

> Jan  3 13:22:19 slim rtpengine[2851285]: DEBUG:
> [6rrus78fn4ao1ap5rp40]: [control] Dump for 'delete' from
> 127.0.0.1:39397: { "call-id": "6rrus78fn4ao1ap5rp40", "via-branch":
> "2", "received-from": [ "IP6", "2001::C9BA" ], "from-tag":
> "s6jcf01ts5", "command": "delete" }
> Jan  3 13:22:19 slim rtpengine[2851285]: INFO: [6rrus78fn4ao1ap5rp40]:
> [core] Scheduling deletion of call branch 's6jcf01ts5' (via-branch
> '2') in 10 seconds

> Jan  3 13:22:19 slim rtpengine[2851285]: DEBUG:
> [6rrus78fn4ao1ap5rp40]: [control] Dump for 'delete' from
> 127.0.0.1:39397: { "call-id": "6rrus78fn4ao1ap5rp40", "via-branch":
> "3", "received-from": [ "IP6", "2001:8AEB" ], "from-tag":
> "s6jcf01ts5", "command": "delete" }
> Jan  3 13:22:19 slim rtpengine[2851285]: INFO: [6rrus78fn4ao1ap5rp40]:
> [core] Scheduling deletion of call branch 's6jcf01ts5' (via-branch
> '3') in 10 seconds
And I guess this happens because there was no `offer` for `via-branch`
tags `2` and `3` (guessing because the offers aren't in the log, but
that's what the stats at the end indicate). The B-side identified by the
branch tags doesn't exist, so it proceeds to delete the A-side based on
the from-tag, which in turn deletes all branches connected to that A-side.

I suppose rtpengine could return an error in this case instead of
deleting the A-side, but either way this is a mistake in usage. Don't
try to delete branches that don't exist. The controlling agent (i.e the
SIP proxy) is in charge of tracking which branches exist and what has
been signalled to rtpengine, not rtpengine (even though it does to some
extent). I'm not sure why this is so difficult to achieve in your use
cases. Perhaps you're better off asking the OpenSIPS guys about how to
properly script this without giving incorrect signalling to rtpengine.

> Just an aside, the via....branch=z9hG4bK...... name is unfortanate and
> misleading. It is not about forking ( from the caller's view, it's
> unaware ). It isn't even mandatory in rfc 2543. Abiding to rfc 3261
> means for the moment, supporting rfc 2543. It is a transaction
> identifier used by a requestor to match a response to a request. It is
> of no interest to down stream devices/nodes. The rtpengine 1/2 options
> in practical terms are no different than providing no via-branch. That
> is where I started my rtpengine journey.
Yes, as I said in Kamailio we have `via-branch=next` for this purpose,
which would use the generated branch tag plus the branch index, which in
turn then matches the tag in the response (where you would then use
`via-branch=1`). This is basically a shortcut to using the branch index
directly as you're doing. (It also solves mistakenly using the same
branch tag "0" in the reverse direction as the generated branch tag is
different on the other leg. To achieve something similar manually you
could concatenate from-tag + branch index for example.) But if you don't
produce an `offer` to rtpengine for every branch, then you need to track
which branch rtpengine was engaged for, and then produce a `delete` only
for those branches, not others.

Cheers

Robert Dyck

unread,
Jan 4, 2022, 3:09:40 PM1/4/22
to rtpengine
Yes, opensips creates its own branch tag when it forwards the request and it is suffixed with the branch index. When the response arrives this via is at the top of the stack. Sure you could use some method in the script to parse the via and obtain the branch index. How is that easier than using the variable that the proxy provides for us?. Option 2 gives us the next via down which gives the caller or possibly an upstream proxy if the the caller configured an outgoing proxy. If there was an outgoing proxy we would have got its via branch with the 1 option and that is what would have been passed to rtpengine. How would stepping further down the via stack help us? If there was an outgoing proxy or any number of proxies upstream only the via branch at the top of the stack will get passed to rtpengine assuming option 1. When a proxy ( location service ) is configured to allow forking calls and there is more than one binding for an AOR ( address of record ) it will create the branches. It only cares about the first answer it gets. It knows which branch this is by the suffix. All other branches are immediatly cancelled. This proxy's via is removed and the next via is used to route the answer upstream. Whatever via branch we obtain with option 1 will get passed to rtpengine. It does not convey any information about branches and will be identical for all branches passed to rtpengine. Only after this are multiple VIA branches produced and this is immediately before forwarding the request(s) and AFTER script processing.

The usual method used by the script writers is to add custom parameters the the route headers. Like via headers, the route headers or only of interest to the node that added them. Parameters added for a request are echoed back for a response. This lets the proxy know about previous actions it had taken for a request associated with the response. I currently use that method to store and retrieve information about rtpengine operations. Unfortunately record-route and route headers are not included in CANCEL requests and the subsequent 487 response. Furthermore the CANCEL requests are internally generated and not exposed in the script only the response. I think I may have to live with multiple ports held up for the duration of a call.

I think it would require a state machine similar to the dialogue module. The dialogue module maintains state information for the duration of the dialogue. The dialogue module itself is of no use here since no SIP dialogue was never achieved in the case of a cancelled branch. Hmm, what about re-offers.

Reply all
Reply to author
Forward
0 new messages