drachtio / drachtio-server

A SIP call processing server that can be controlled via nodejs applications
https://drachtio.org
MIT License
237 stars 92 forks source link

Drachtio server + RTPEngine solution with two network interfaces #136

Open joelact opened 4 years ago

joelact commented 4 years ago

Hello @davehorton.

I'm setting up a Drachtio server + RTPEngine solution with two network interfaces. I have a network interface (eth0) with NAT that is publicly accessible and another network interface (eth1) that is part of a private network.

I'm using an app written in Typescript connected to Drachtio server that uses the function createB2BUA to bridge one call between two endpoints.

The first endpoint is an Asterisk instance that originates a call to a second endpoint (phone01) which is a user previously registered on Drachtio.

The Asterisk is connected to Drachtio on the eth1 (private interface), while the Phone is registering through eth0 (public interface).

I'm expecting that Drachtio receives the Invite from Asterisk on the eth1 and that it uses the eth0 to connect to the Phone.

As you can see in the attached image, Drachio Server is using the eth1 to send the Invite to the Phone instead of the expected eth0. Since this interface has no connection to the public network, the call is falling.

Can you please give me a hint if there is some issue with our Drachtio Server configuration (attached)?

This is the function I'm using to originate the call:

const callTo = (
    logger: Logger,
    srf: Srf,
    req: Request,
    res: Response,
    destination: string,
    direction: string,
): void => {
    const callId: string = req.get('Call-Id');
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const from: Record<string, any> = req.getParsedHeader('From');

    logger.info(`callTo :: To :: ${JSON.stringify(from)}`);

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const details: Record<string, any> = {
        'call-id': callId,
        'from-tag': from.params.tag,
    };

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const optsOffer: Record<string, any> = Object.assign({}, details, { sdp: req.body });

    if (direction === 'external') {
        optsOffer.direction = ['priv', 'pub'];
        optsOffer.ICE = 'force';
        optsOffer.DTLS = 'passive';
        optsOffer.flags = ['generate mid'];
    } else if (direction === 'internal') {
        optsOffer.direction = ['pub', 'priv'];
    } else {
        logger.error('Direction not defined', callId);
        return;
    }

    const RTPEngine: Client = new Client();

    RTPEngine.offer(config.RTPEngine, optsOffer)
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        .then((rtpResponse: any) => {
            if (rtpResponse && rtpResponse.result === 'ok') {
                return rtpResponse.sdp;
            }
            throw new Error('rtpengine failure');
        })
        .then((sdpB: string) => {
            const customHeaders: string[] = Object.keys(req.msg.headers).filter((elem: string) =>
                elem.toUpperCase().startsWith('X-'),
            );

            logger.info(`REQUEST :: ${JSON.stringify(req.msg)}`);

            return srf.createB2BUA(
                req,
                res,
                destination,
                {
                    localSdpB: sdpB,
                    localSdpA: getSdpA(RTPEngine).bind(null, details),
                    proxyRequestHeaders: customHeaders,
                },
                {
                    cbRequest: (err: Error, req: Request) => {
                        logger.info(`REQUEST TO OUTSIDE: ${JSON.stringify(req)}`);
                    },
                    // eslint-disable-next-line @typescript-eslint/no-empty-function
                    cbProvisional: () => {},
                    // eslint-disable-next-line @typescript-eslint/no-empty-function
                    cbFinalizedUac: () => {},
                },
            );
        })
        .then(({ uas, uac }: Record<string, Dialog>) => {
            return endCall(RTPEngine, uas, uac, details);
        })
        .catch((err: Error) => {
            logger.info(`DEBUG | ${JSON.stringify(err)}`, callId);
            logger.error(`Error proxying call with media | ${err}`, callId);
        });
};

Drachtio server configuration:

<drachtio>
    <admin port="9022" secret="cymru">10.35.213.251</admin>

    <sip>
        <contacts>
            <contact>sip:10.35.213.251:5060;transport=udp</contact>
            <contact external-ip="88.0.0.1">sip:10.35.210.251:5060;transport=udp</contact>
            <contact external-ip="88.0.0.1">sip:10.35.210.251:443;transport=ws</contact>
        </contacts>

        <capture-server port="9060" hep-version="3" id="1035213251">10.0.0.3</capture-server>

        <spammers action="reject" tcp-action="discard">
            <header name="User-Agent">
                <value>sip-cli</value>
                <value>sipcli</value>
                <value>friendly-scanner</value>
            </header>
            <header name="To">
                <value>sipvicious</value>
            </header>
        </spammers>
    </sip>

    <cdrs>true</cdrs>

    <logging>
        <file>
            <name>/var/log/drachtio/drachtio.log</name>
            <archive>/var/log/drachtio/archive</archive>
            <size>100</size>
            <maxFiles>20</maxFiles>
            <auto-flush>true</auto-flush>
        </file>

        <sofia-loglevel>3</sofia-loglevel>

        <loglevel>info</loglevel>
    </logging>

</drachtio>

Attachments:

INVITE REGISTER

Additional system information

Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.35.210.1 0.0.0.0 UG 100 0 0 eth0 10.35.210.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0 10.35.213.0 0.0.0.0 255.255.255.0 U 101 0 0 eth1

Thank you for your help, if you need any additional information feel free to ask.

Best regards.

alexandremiguelabreu commented 4 years ago

Hi @davehorton,

Can you help us with this?

davehorton commented 4 years ago

I believe the problem is that the drachtio server does not currently select the interface based on the destination ip..it selects only based on protocol. This is something I have been wanting to address for a while. Are you interested in testing a branch where I try to make some fixes?

In the meanwhile, could you confirm this workaround for me -- have the phone register over tcp, and change the config for the internal address in drachtio.conf.xml to listen for both udp and tcp on that contact. I believe the call will now get delivered to the phone

alexandremiguelabreu commented 4 years ago

Hi @davehorton,

Yes, we are available to test your fix branch.

We have done the test you asked and we confirm that, when the REGISTER was made by TCP, the Drachtio server chooses the right interface to deliver the INVITE.

When you have the branch to test please let us know.

davehorton commented 4 years ago

Could you please build the interface-selection-improvements branch, and then retry your tests? If you can provide the drachtio server log file with loglevel 'debug' once you have tested that would be useful to me

davehorton commented 3 years ago

Note that the latest changes on the above-mentioned branch (interface-selection-improvements) include changes to the underlying sofia sip library so after pulling the latest make sure to do

git submodule update --init --recursive

before rebuilding

davehorton commented 3 years ago

could you try with the latest on this branch when you have a chance? Do make sure to update and rebuild dependencies as before

davehorton commented 3 years ago

hmm, actually hold off a bit. I am still runnning some tests..

davehorton commented 3 years ago

OK if you can try your tests with the tip of this branch I would appreciate it

davehorton commented 3 years ago

The main issue here has been that you are sending to a DNS name, and DNS resolution has been occurring after the selection of the IP/interface to use. In the recent checkin on this branch I changed that, so first we resolve the dns name before selecting the interface. Additionally, you will need to specify the CIDR of each interface in the drachtio.conf.xml file, like so

<contacts>
            <contact local-net="10.35.213.0/24">sip:10.35.213.251:5060;transport=udp</contact>
            <contact external-ip="88.0.0.1" local-net"10.35.210.0/24">sip:10.35.210.251:5060;transport=udp</contact>
            <contact external-ip="88.0.0.1" local-net"10.35.210.0/24">sip:10.35.210.251:443;transport=ws</contact>
        </contacts>

Can you try this with the latest on the branch when you have a chance?

Also, you had a crash in a scenario with a CANCEL in the log you sent me, and I would really like to get to the bottom of that.

vitor-vidal commented 3 years ago

Hi @davehorton

We've done some tests with the most recent commit of the branch "interface-selection-improvements" and we had the following results:

On this last test, we are receiving a "503 Service Unavailable" and the INVITE is not sent to the Asterisk. You can see the Drachtio log appended.

Regarding the CANCEL, we were not able to reproduce it yet. I'll let you know when we have some news on this topic.

drachtio.log

davehorton commented 3 years ago

The failure case seems to be because the INVITE request is too large to be sent over udp:

2020-09-29 18:17:25.246605 nta.c:8686 outgoing_print_tport_error() nta: INVITE (26146130): Message too long (90) with UDP/[10.35.213.132]:5060

If you have not done so already, could you modify your configuration to set the mtu for udp to 4096 ?

If you have already done so, let me know as there must be a different problem...

vitor-vidal commented 3 years ago

We've changed the MTU for UDP to 4096 and it fixed our problem. We are now able to originate and receive calls both with UDP and with WSS.

Unfortunately, we've detected another issue with our scenario when we make the following test:

If the softphone rejects the call, the Drachtio Server uses the wrong interface to send the ACK: image drachtio1.log

The webphone equivalent scenario is working as expected: Asterisk (UDP with internal IP) -> Drachtio Server -> webphone (WSS with external IP)

davehorton commented 3 years ago

Is it possible you could recreate this failure with sofia log level turned up to 9? In this case (failure response) the sofia sip stack generates the ACK itself and I would like to see more logging about that

vitor-vidal commented 3 years ago

Hi Dave,

Here is the log of the test that you've required (Sofia log level turned up to 9).

Please let me know if you need any additional information.

Thank you.

drachtio2.log

vitor-vidal commented 3 years ago

Hello @davehorton ,

Just wanna let you know that we are still available to make some tests and to get you additional information about this issue.

If there's something we can do to help you, please let me know.

davehorton commented 3 years ago

Sorry, been off on some other projects myself. I should be able to re-engage on it this week.

vitor-vidal commented 3 years ago

Since I understand how tricky this issue is, I was just trying to understand if I could provide you any additional info/data to help.

Thank you, once again, for your support.