asterisk / asterisk-external-media

Apache License 2.0
55 stars 26 forks source link

No communication between local server and Asterisk in container #20

Open gtjoseph opened 11 months ago

gtjoseph commented 11 months ago

@gtjoseph Could the Asterisk version be a plausible cause? I looked through the changelog but couldn't find any ARI related breaking changes. In my case, everything seems to be fine but the audio server running locally (host) just doesn't receive any packages from Asterisk 20 running within Docker.

Originally posted by @jneuendorf-i4h in https://github.com/asterisk/asterisk-external-media/issues/11#issuecomment-1664608700

gtjoseph commented 11 months ago

Can you run Asterisk on the host just as a test and see if it works? What kind of networking are you using with docker? Can you connect to asterisk's built-in web server from a browser on the host?

jneuendorf-i4h commented 11 months ago

First of all, thanks for the quick response! 🙏

Can you run Asterisk on the host just as a test and see if it works?

My setup is on macOS and I am not sure if Asterisk can be installed on macOS as easily as on Debian/Ubuntu. Therefore, I have not tried this approach yet.

What kind of networking are you using with docker?

So far, I stuck with the defaults. With network_mode: host (docker-compose.yml) phone calls no longer worked.

Can you connect to asterisk's built-in web server from a browser on the host?

  1. Yes, on my 1st attempt, I started the ARI client on the host and the connection to the REST API worked (authentication, channel creation etc.)
  2. 2nd attempt: Because I suspected networking problems due to Docker, I started the ARI client from within docker (so everything on localhost/127.0.0.1 within Docker. But the behavior stayed the same. 🤔

I actually simplified/adjusted this repo's code for the start, as I want to initiate a call to an SIP device (PJSIP/6001) and stream the audio from/to the audio server (without any processing for now).

Any advice or hint would be very helpful, so let me know if anything is unclear or if there is information missing. Also, debugging tips are very welcome (so far, I've only used ari set debug all on). 😉

For better insights, here my code:

Client

src/upd.mjs

Start the audio server in a separate script to reduce complexity in the main script (for debugging).

import { RtpUdpServerSocket } from "../lib/rtp-udp-server.js"

const audioServer = new RtpUdpServerSocket(
    '0.0.0.0:9999',
    false,
    "./test.wav",
)
console.log(audioServer)

src/send.mjs

Send a test message to the audio server.

import dgram from 'node:dgram'
import { Buffer } from 'node:buffer'

const message = Buffer.from('A few bytes to send for testing purposes...')
const client = dgram.createSocket('udp4')
client.send(message, 9999, 'localhost', (err) => {
    console.log('error', err)
    client.close()
})

src/main.mjs

Start the ARI application, setup the channels and bridge, and make the call.

import ari from 'ari-client'

const options = {
    dialstring: "PJSIP/6001",
    format: "ulaw",
    listenServer: '127.0.0.1:9999',
    stasisApp: "external-media",
}

ari.connect(
    'http://127.0.0.1:8088',
    'asterisk',
    'asterisk',
    async (error, client) => {
        if (error) {
            console.error(error)
            console.warn("CLOSING")
        }

        await client.start(options.stasisApp)

        // Create a simple mixing bridge that is controlled by ARI/Stasis
        const mixingBridge = client.Bridge()
        try {
            await mixingBridge.create({type: "mixing"})
            console.log("created mixing bridge")
        } catch(error) {
            console.error(error)
            console.warn("CLOSING")
        }
        mixingBridge.on('BridgeDestroyed', (event) => {
            console.log("destroyed mixing bridge")
            console.warn("CLOSING")
        })

        const outboundChannel = client.Channel()
        outboundChannel.on('StasisStart', (event, {id}) => {
            mixingBridge.addChannel({channel: id})
            console.log("added outbound channel to mixing bridge", id)
        })
        outboundChannel.on('StasisEnd', (event, chan) => {
            console.warn("CLOSING")
        })

        // Call the phone or confbridge specified in dialstring
        try {
            await outboundChannel.originate({
                endpoint: options.dialstring,
                formats: options.format,
                app: options.stasisApp,
            })
            // localChannel.dial()
        } catch (error) {
            console.error(error)
            console.warn("CLOSING")
        }

        // Now we create the External Media channel.
        const externalChannel = client.Channel()
        externalChannel.on('StasisStart', (event, {id}) => {
            mixingBridge.addChannel({channel: id})
            console.log("added external channel to mixing bridge", id)
        })
        externalChannel.on('StasisEnd', (event, chan) => {
            console.warn("CLOSING")
        })

        /*
         * We give the external channel the address of the listener
         * we already set up and the format it should stream in.
         */
        const externalMediaChannel = await externalChannel.externalMedia({
            app: options.stasisApp,
            // external_host: options.listenServer,
            external_host: "127.0.0.1:9999",
            format: options.format,
        })
        console.log("externalMediaChannel", externalMediaChannel)

        setTimeout(async () => {
            console.warn("STOPPING")
            outboundChannel && await outboundChannel.hangup()
            externalChannel && await externalChannel.hangup()
            mixingBridge && await mixingBridge.destroy()
            client.stop()
        }, 30 * 1000);
})

Docker

docker-compose.yml

version: '3'
services:
  asterisk:
    image: asterisk
    build:
      context: .
      dockerfile: docker/asterisk/Dockerfile
    # network_mode: host
    ports:
      - "8088:8088"                   # ARI HTTP
      - "5060:5060/udp"               # SIP UDP
      - "5060:5060"                   # SIP TCP
      - "5061:5061"                   # SIP TLS
      - "10000-10099:10000-10099/udp" # RTP
      - "9999:9999/udp" # test stream from client on host to audio server in Docker

Server

ari.conf

[general]
enabled = yes
pretty = yes
allowed_origins = *

[asterisk]
type = user
read_only = no
password = asterisk

http.conf

[general]
enabled = yes
bindaddr = 0.0.0.0
bindport = 8088

[asterisk]
type = user
read_only = no
password = asterisk

rtp.conf

[general]
rtpstart = 10000
rtpend   = 10099

strictrtp = no

extensions.conf

[from-internal]
exten => 100,1,Answer()
 same => n,Stasis(external-media)
 same => n,Hangup()

pjsip.conf

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0

[6001]
type=endpoint
context=from-internal
disallow=all
allow=ulaw
auth=6001
aors=6001

[6001]
type=auth
auth_type=userpass
password=unsecurepassword
username=6001

[6001]
type=aor
max_contacts=1
mattbonnell commented 8 months ago

seeing the same thing here; listening on port 9999 with netcat but doesn't seem anything is coming through.

vlxdisluv commented 3 months ago

have you solved you problem? @jneuendorf-i4h

jneuendorf-i4h commented 3 months ago

Thanks for catching up @vlxdisluv! The currently working solution is running Asterisk not in a Docker container but in a (Proxmox) VM (LXC untested).

A reasonable cause is network_mode: host on macOS:

The host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac https://docs.docker.com/network/drivers/host/

So I guess, the issue can be closed, even though it would be helpful to have documentation for running Asterisk in Docker (on specific machines/operating systems).

vlxdisluv commented 3 months ago

@jneuendorf-i4h I have successfully configured Asterisk in Docker container (macOS m1 arm) because it can establish a peer-to-peer connection (between two softphones), but I don't have enough knowledge to write a proper Asterisk configuration for a workflow with external media. I need to initiate an outgoing ARI call to a softphone and receive real-time RTP packets and then send them to some STT provider. In this case, I also established a connection, but I cannot receive RTP packets. Could you share your working Asterisk configs?

jneuendorf-i4h commented 3 months ago

@jneuendorf-i4h I have successfully configured Asterisk in Docker container (macOS m1 arm) because it can establish a peer-to-peer connection (between two softphones), but I don't have enough knowledge to write a proper Asterisk configuration for a workflow with external media. I need to initiate an outgoing ARI call to a softphone and receive real-time RTP packets and then send them to some STT provider. In this case, I also established a connection, but I cannot receive RTP packets. Could you share your working Asterisk configs?

Have you checked the audio signal of your p2p/softphone connection? I would not be surprised if the connection can be established while not receiving RTP packets. RTP debugging can be done via several different approaches, e.g.

On the JS client side, you could try (using the nomenclature from above)

setInterval(async () => {
    const rtpStats = await outboundChannel.rtpstatistics()
    console.log("RTP", rtpStats)
}, 5000)