drachtio / drachtio-rtpengine-webrtcproxy

Webrtc proxy server built using drachtio (SIP Proxy) and rtpengine (RTP)
MIT License
39 stars 19 forks source link

rtpengine.offer is having issues #4

Open sadafsaeed10p opened 5 years ago

sadafsaeed10p commented 5 years ago

In my scenario, user will be making call from some SIP client e.g. Blink, which will be handled by the 'invite' handler of drachtio, at this place I've the SDP of caller i.e. coming from Blink, now the application will then interact with a webrtc client (the other party to interact with) to get its SDP and exchange the two SDPs between them to make a connection, here we need to make this happen via webrtc, for which we've installed rtpengine, currently the problem I'm facing is that whenvever I called rtpengine.offer method, it never comes back (not even in catch flow), instead it cancels the call after few seconds.

davehorton commented 5 years ago

It sounds like either the 'offer' message is not making it to rtpengine, or the response from rtpengine is not making it back. Can you check the rtpengine logs first, to make sure it is receiving the 'offer' command? If so, the issue may be that the ip address the drachtio app is running on is not reachable from rtpengine....is the drachtio app behind a nat or something? What IPs are the app and rtpengine running on?

sadafsaeed10p commented 5 years ago

Thanks for the quick reply, I'm running rtpengine docker using below command:

docker run -it --rm --name rtpengine -p 22222:22222/udp rtpengine

Previously I was not providing port like this '22222:22222/udp', it might causing that issue, after making this change it now gets back from the rtp.offer() method but its now giving an error.

Error: {"recordings":["typ host\r\na=candidate:Hac10111c 2 UDP 2130706430 172.16.17.28 49516 t"],"result":"error","error-reason":"Ran out of ports"} Error proxying call with media: Error: rtpengine failure: Error: rtpengine failure

Steps I'm doing -drachtio is running at 172.17.0.2, Im using below command to run drachtio: docker run -it --rm --name drachtio -p 9022:9022 -p 5060:5060/udp -p 5060:5060/tcp drachtio/drachtio-server drachtio

-Now after running these two (i.e. drachtio & rtpengine) have registered a user (which was created on Blink) on drachtio i.e. on 172.17.0.2, then added a contact in my Blink client whose sip address is sip:testuser@172.16.17.28 (here 172.16.17.28 is my machine's IP address), once I made a call it reaches to the 'invite' handler but sends the above mentioned error in response

Code `srf.invite((req, res) => {

const rtpConfig = { "host": "127.0.0.1", "port": 22222, "local-port": 2223 };

rtpengine.offer(rtpConfig, Object.assign(details, { 'sdp': req.body, 'record call': 'yes' }))
    .then((rtpResponse) => {
        console.log(got response from rtpengine: ${JSON.stringify(rtpResponse)});
        if (rtpResponse && rtpResponse.result === 'ok') return rtpResponse.sdp;
        throw new Error('rtpengine failure');
    })`
davehorton commented 5 years ago

which docker image are you using?

I think you need to expose the rtp ports as well. Try this and let me know if this resolves the error you are seeing:

docker run -it --rm --name rtpengine -p 22222:22222/udp -p 23000:23999 rtpengine
davehorton commented 5 years ago

actually, try this:

docker run -it --rm --name rtpengine -p 22222:22222/udp -p 23000:23999 rtpengine \
 --port-min 23000 --port-max 2399
sadafsaeed10p commented 5 years ago

I'm providing the min & max ports from the config, here are my config details: [rtpengine] interface=172.16.17.28 foreground=true log-stderr=true listen-ng=22222 port-min=23000 port-max=23999 recording-dir=/tmp recording-method=pcap recording-format=eth log-level=6 delete-delay=0 The command which you have provided in your earlier message is giving an error: Error: /entrypoint.sh: line 41: exec: --: invalid option

I've cloned the repository from https://github.com/davehorton/docker-rtpengine

Here is the error log after making call from Blink to my own IP:

[1559625897.292638] INFO: Creating metadata directory "/tmp/metadata". [1559625897.292740] INFO: Creating pcaps directory "/tmp/pcaps". [1559625897.292774] INFO: Creating tmp directory "/tmp/tmp". [1559625897.294571] INFO: Generating new DTLS certificate [1559625897.301099] INFO: Startup complete, version 7.4.0.0+0~mr7.4.0.0 git-master-7ad9906 [1559625929.785738] INFO: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Received command 'offer' from 172.17.0.1:45644 [1559625929.785830] NOTICE: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Creating new call [1559625929.785872] NOTICE: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Turning on call recording. [1559625929.786095] INFO: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Writing recording file: /tmp/pcaps/e09ae08a-6750-4fca-83fa-ca281df1fb0a-1a1c2c125186d64f.pcap [1559625929.793684] ERR: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Failed to get 2 consecutive ports on interface 172.16.17.28 for media relay (last error: Cannot assign requested address) [1559625929.793699] ERR: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Failed to get 2 consecutive ports on all locals of logical 'default' [1559625929.793709] ERR: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Error allocating media ports [1559625929.793778] WARNING: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Error writing SDP body to metadata file: Cannot assign requested address [1559625929.793789] ERR: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Destroying call [1559625929.793802] INFO: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Final packet stats: [1559625929.793812] INFO: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: --- Tag 'e30ada86-7f6a-4d8f-9cdf-a0af9b540d68', created 0:00 ago for branch '', in dialogue with '' [1559625929.793820] INFO: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: ------ Media #1 (audio over RTP/AVP) using unknown codec [1559625929.793826] INFO: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: --- Tag '', created 0:00 ago for branch '', in dialogue with 'e30ada86-7f6a-4d8f-9cdf-a0af9b540d68' [1559625929.793833] INFO: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: ------ Media #1 (audio over RTP/AVP) using unknown codec [1559625929.793841] ERR: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Call start seems to exceed call stop [1559625929.794003] INFO: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Moved metadata file "/tmp/tmp/rtpengine-meta-e09ae08a-6750-4fca-83fa-ca281df1fb0a-1a1c2c125186d64f.tmp" to "/tmp/metadata" [1559625929.794029] WARNING: [e09ae08a-6750-4fca-83fa-ca281df1fb0a]: Protocol error in packet from 172.17.0.1:45644: Ran out of ports [d7:call-id36:e09ae08a-6750-4fca-83fa-ca281df1fb0a7:command5:offer8:from-tag36:e30ada86-7f6a-4d8f-9cdf-a0af9b540d6811:record call3:yes3:sdp731:v=0 o=- 3768614729 3768614729 IN IP4 172.17.0.1 s=Blink 3.2.0 (Linux) t=0 0 m=audio 56751 RTP/AVP 113 9 0 8 101 c=IN IP4 172.16.17.28 a=rtcp:49945 a=rtpmap:113 opus/48000/2 a=fmtp:113 useinbandfec=1 a=rtpmap:9 G722/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-16 a=zrtp-hash:1.10 19b768b625bcef7b2de2d1d7fd96252da01fffe270a3da7213146f209acf4138 a=ice-ufrag:7bdf12d4 a=ice-pwd:4b1b078b a=candidate:Hac10111c 1 UDP 2130706431 172.16.17.28 56751 typ host a=candidate:Hac110001 1 UDP 2130706431 172.17.0.1 56751 typ host a=candidate:Hac10111c 2 UDP 2130706430 172.16.17.28 49945 typ host a=candidate:Hac110001 2 UDP 2130706430 172.17.0.1 49945 typ host a=sendrecv e]

sadafsaeed10p commented 5 years ago

The above issue has been resolved by increasing the number of ports in the config file, although its a workaround but after making following change it started working, port-min=23000 port-max=50000 (Previously it was 23999)

Now I'm facing another issue which comes in rtpengine.answer method.

Scenario is, an account has been registered on drachtio on machine A, I called on my own IP using Blink, when it comes on 'invite' handler, rtpengine.offer is invoked to call other sip user (on some other machine say Machine B). Below is my code for offer creation:

rtpengine.offer(rtpConfig, 
Object.assign(details, { 'sdp': req.body, 'record call': 'yes' }))
  .then((rtpResponse) => {
    console.log(`got response from rtpengine: ${JSON.stringify(rtpResponse)}`);
    if (rtpResponse && rtpResponse.result === 'ok') 
      return rtpResponse.sdp;
    throw new Error('rtpengine failure');
 })
.then((sdpB) => {
  console.log(`rtpengine offer returned sdp ${sdpB}`);
  return srf.createB2BUA(req, res, dest, {
  localSdpB: sdpB,
  localSdpA: getSdpA.bind(null, details)
 });
})
.then(({ uas, uac }) => {
  console.log('call connected with media proxy');
  uas.on('destroy', () => uac.destroy());
  uac.on('destroy', () => uas.destroy());
})
.catch((err) => {
  console.error(`Error proxying call with media: ${err}: ${err.stack}`);
});

When the srf.createB2BU method being called, it triggers the 'invite' event on other machine i.e. Machine B as I've passed its sip address as destination from machine A, now I'm creating answer from machine B, below is the code of creating answer from machine B to A.

const rtpConfig = {
          "host": "IP Address of Machine A where rtpengine is  running",
          "port": 22222,
          "local-port": 2223
    };
    return rtpengine.answer(rtpConfig, Object.assign(details, {
            'sdp': remoteSdp,
            'to-tag': res.getParsedHeader('To').params.tag,
            'ICE': 'remove'
    }))
        .then((response) => {
            if (response.result !== 'ok') 
                 throw new Error(`Error calling answer: ${response['error-reason']}`);
            return response.sdp;
        });

Creating answer throws an error at runtime. Error: No to-tag in message. Stack Trace:

(node:24899) UnhandledPromiseRejectionWarning: Error: Error calling answer: No to-tag in message
    at rtpengine.answer.then (/home/user/Desktop/sip-sample-application/rtp.js:72:49)
    at <anonymous>
warning.js:18
(node:24899) UnhandledPromiseRejectionWarning: Unhandled promise rejection. 
This error originated either by throwing inside of an async function without a catch block, 
or by rejecting a promise which was not handled with .catch(). (rejection id: 2)

My question is where should I get this 'to-tag' to create an answer or is there anything I'm doing wrong?

davehorton commented 5 years ago

A few things:

First, I see that I had an issue in a recent checkin to drachtio/rpengine:latest. I've fixed and rebuilt, please pull and use that. The command line arguments do work to limit the port range:

docker run -it --rm --name rtpengine -p 22222:22222/udp -p 23000:23999 \
drachtio/rtpengine:latest rtpengine --port-min 23000 --port-max 23099
[1559650230.841443] INFO: Creating metadata directory "/tmp/metadata".
[1559650230.841525] INFO: Creating pcaps directory "/tmp/pcaps".
[1559650230.841579] INFO: Creating tmp directory "/tmp/tmp".
[1559650230.843735] INFO: Generating new DTLS certificate
[1559650230.885368] INFO: Startup complete, version 7.4.0.0+0~mr7.4.0.0 git-master-f960799

If you still find you are getting that error about no ports available, that would be very strange.

As far as your specific scenario, it sounds like you are running two drachtio servers, and the call flow is client A --> server A --> server B --> client B

Is there some reason you are running two drachtio sip servers in this scenario? i.e. why aren't clients A and B registering to the same server?

This is leading to your problem, which is that you are calling rtpengine#answer from server B, whereas the rtpengine#offer was called from server A. And you are calling answer from server B when you are receiving an INVITE -- at that point the INVITE will not have a To tag, which is required in the answer command.

This all seems needlessly complex to me, but maybe I don't understand what you are trying to accomplish.

If you want to see an example of an app that allows sip phones to register and call each other, and webrtc clients to do the same, and further to make and receive calls on a SIP trunk to the PSTN, please have a look at https://github.com/davehorton/drachtio-basic-registrar.

sadafsaeed10p commented 5 years ago

Here is my use case,

We have a SIP proxy server say example1.com(a typical SIP Proxy) which makes a call to a SIP proxy server say example2.com(built using drachtio) on address callee@example2.com from address caller@example1.com, The user on example2.com is basically a WebRTC client, while the user on example1.com is a typical sip client.

The caller sends an invite to the callee, and the callee sends the answer respectively. Both the users exchange their respective SDPs along with the ICE candidates information. Once that is successfully exchanged, we will create a P2P session between these two users allowing them to call seamlessly.

davehorton commented 5 years ago

OK, I think the issue is you are not understanding exactly how rtpengine 'offer' and 'answer' work. You shouldn't be calling 'offer' from the UAC side of the callflow, and then 'answer' from the UAS side. You call offer / answer as a pair, both from the same side (either UAC, or UAS, or both).

Stepping back, you need to decide whether you need to run two instances of rtpengine or one. You basically have two SIP elements here, and it's not clear to me if they have different functions.

example1.com is receiving SIP calls over plain UDP/RTP from what I understand. It sounds like it may either be a simple outbound proxy, or it may also be acting as a registrar for the example1.com domain. If all you care about is outbound calls from example1 clients (as described in your callflow), then you don't necessarily need it to have an rtpengine instance, but if you want to deliver inbound calls to example1 clients then it should have rtpengine instance.

example2.com is handling webrtc clients, so it definitely needs an rtpengine instance. Its also acting as a registrar.

When your app on example1.com receives an INVITE, it calls 'offer' to allocate an endpoint. It then sends an INVITE to example2.com with the sdp provided. Forget about what happens on example2.com for the moment, but when the 18x/200 response comes back to your example1.com app with an sdp, that is when you call 'answer'. You take the sdp from that and respond to caller@example1.com with it.

Completely separately, over on your app on example2.com, when you receive an INVITE you similarly call 'offer' (note this has a different call-id and is a completely different rtpengine transaction than what happened back on example1.com). You then look up callee@example2.com and send an INVITE there with the sdp response from the offer. When you get a 18x/200 with an sdp from the far end device, you call answer, get an sdp and respond back to example1.com with that sdp.

So you are calling two sets of offer/answer pairs, each from one of the servers.

https://github.com/davehorton/drachtio-basic-registrar may be a better example for you to look at